Я пытаюсь сопоставить строки, которые не содержат последовательность символов, используя sed. Согласно документации sed (https://www.gnu.org/software/sed/manual/sed.html), вы используете ()
в sed при использовании ERE (опция -E) для создания групп, а затем {}
для захвата повторения предыдущего токена. Однако я хочу сопоставить строки, которые не содержат эту группу (которая также включает в себя другие элементы, которые я хочу сопоставить).
Вот содержимое test_file:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin
Это всего лишь варианты одной и той же строки для проверки моей команды sed. По сути, я хочу изменить строки, которые не содержат пути /home/tlytle/bin
, добавив :/home/tlytle/bin
.
Вот мой мыслительный процесс, когда я создаю свой ERE для sed:
Во-первых, я хочу захватить все строки, содержащие /home/tlytle/bin.
$ sed -E '\|/home/tlytle/bin| s|$|:/home/tlytle/bin|' test_file
Выход:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Все идет нормально. sed добавил :/home/tlytle/bin
в конец единственной строки, которая уже содержала :/home/tlytle/bin
. Теперь я хочу создать для этого группу. Итак, я делаю это:
$ sed -E '\|(/home/tlytle/bin)| s|$|:/home/tlytle/bin|' test_file
Выход:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Все еще выглядит хорошо. Теперь, в качестве теста, я хочу сопоставить его 1 или несколько раз. Я мог бы использовать здесь «+», но я хочу проверить конструкцию повторения, просто чтобы убедиться, что я не схожу с ума:
$ sed -E '\|(/home/tlytle/bin){1,}| s|$|:/home/tlytle/bin|' test_file
Выход:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Похоже, он работает хорошо для меня. Теперь я хочу, чтобы это соответствовало ровно нулю из них. И вот здесь все ломается:
$ sed -E '\|(/home/tlytle/bin){0}| s|$|:/home/tlytle/bin|' test_file
Выход:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Похоже, что ERE соответствует каждой строке. Я думал, что это будут только совпадающие строки, не содержащие /home/tlytle/bin
.
Может ли кто-нибудь объяснить мне, почему это не делает то, что, по моему мнению, должно делать? Есть ли другая конструкция ERE, которую мне следует использовать?
Используйте оператор !
после регулярного выражения для обработки строк, которые не соответствуют регулярному выражению. Например. /foo/!s/abc/def/
выполнит замену во всех строках, которые не содержат foo
.
Используя GNU sed, сравните результаты <<<$'aabc\nxabc' sed -E 's/(aa){0}bc/ \&=& \\1 = "\1"/'
и <<<$'aabc\nxabc' sed -E 's/(aa){0,}bc/ \&=& \\1 = "\1"/'
.
sed отлично подходит для простых операций s/old/new в отдельных строках, для всего остального просто используйте awk, например используя любой awk:
$ awk -v reqd='/home/tlytle/bin' '{print $0 (index($0,reqd) ? "" : ":"reqd)}' file
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin
При этом я сохраняю требуемый сегмент пути в переменной (-v reqd='/home/tlytle/bin'
), затем печатаю текущую строку (print $0
), за которой следует троичное выражение, внутри которого я выполняю сравнение строк, чтобы увидеть, присутствует ли уже эта требуемая строка ( index($0,reqd)
) и, если да, ничего больше не выводить (""
), в противном случае выведите двоеточие, за которым следует требуемая строка (":"reqd
).
Если ваша целевая строка может содержать обратную косую черту, вам нужно передать ее в awk, используя ENVIRON[]
или аналогичный, см. Как использовать переменные оболочки в awk-скрипте?, и если ваша целевая строка может быть подстрокой какой-либо другой входной строки, с которой вы не хотите сопоставляться, например. /foo/home/tlytle/binary
, то вам нужно будет настроить код, чтобы справиться с этим.
Что касается кода в вашем вопросе, сопоставление нулевых повторений чего-либо будет соответствовать любому вводу, независимо от того, присутствует ли это «что-нибудь» или нет:
$ echo 'foo' | sed -En '/(bar){1,}/p'
$ echo 'bar' | sed -En '/(bar){1,}/p'
bar
$
$ echo 'foo' | sed -En '/(bar){0}/p'
foo
$ echo 'bar' | sed -En '/(bar){0}/p'
bar
$
поэтому попытка сделать что-то путем сопоставления при нулевом повторении чего-либо всегда будет неудачной.
Вы можете сделать то, что вы пытались сделать, по крайней мере, в GNU sed (возможно, и в других, я не знаю) с оператором !
(not), чтобы проверить, не удалось ли предыдущее сравнение регулярных выражений:
$ echo 'foo' | sed -En '/(bar)/!p'
foo
$ echo 'bar' | sed -En '/(bar)/!p'
$
и затем вы можете сделать это для вашей текущей проблемы:
$ reqd='/home/tlytle/bin'; sed '\#'"$reqd"'#!s#$#:'"$reqd"'#' file
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
#Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/home/tlytle/bin
Defaults secure_path = /sbin:/bin:/home/tlytle/bin:/usr/sbin:/usr/bin
но учтите, что в версии sed, в дополнение к соображениям, которые я перечислил ниже сценария awk, вы также должны надеяться, что в этой строке нет символов #
(или любого другого символа-разделителя, который вы выберете), метасимволов регулярного выражения или метасимволов обратной ссылки, см. Можно ли надежно экранировать метасимволы регулярных выражений с помощью sed, потому что в sed нет конструкций для обработки литеральных строк, в отличие от awk.
Как говорили другие, просто используйте Bang, чтобы исправить свой sed:
sed '\|/home/tlytle/bin|!s|$|:/home/tlytle/bin|' test_file
А если вас беспокоит необходимость экранирования специальных символов, используйте Perl и его способность экранировать с помощью \Q..\E:
perl -pe '$p = "/home/tlytle/bin"; s/$/:$p/ unless /\Q$p\E/' test_file
Другой способ: напишите шаблон с двумя ветвями. Первый — это цель, которая будет перезаписана, второй — конец строки:
sed -E 's#:/home/tlytle/bin|$#:/home/tlytle/bin#' test_file
О кванторе {0}
: группа, класс или отдельный символ с этим квантором описывают пустую строку (которая выполняется каждый раз, везде в строке и для всех строк). Это не отрицание. Имейте в виду, что выкройка — это описание, а не список пожеланий.
+
то же самое, что{1,}
,*
то же самое, что*
, но с 0 или более совпадениями тоже то же самое, что{0,}
.?
будет таким же, как{0}
. Чего вы пытаетесь достичь??
или{0}
означает, что предыдущий поиск не обязательно должен совпадать, поэтому будет найдено что угодно. Я бы запустил ваши регулярные выражения на regex101. Обычно это показывает вам, что выполняется. Насколько я знаю, у них нет движка POSIX. Вы пытаетесь отменить совпадение/home/tlytle/bin
?