Как сопоставить группу ровно ноль раз

Я пытаюсь сопоставить строки, которые не содержат последовательность символов, используя 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, которую мне следует использовать?

+ то же самое, что {1,}, * то же самое, что *, но с 0 или более совпадениями тоже то же самое, что {0,}. ? будет таким же, как {0}. Чего вы пытаетесь достичь? ? или {0} означает, что предыдущий поиск не обязательно должен совпадать, поэтому будет найдено что угодно. Я бы запустил ваши регулярные выражения на regex101. Обычно это показывает вам, что выполняется. Насколько я знаю, у них нет движка POSIX. Вы пытаетесь отменить совпадение /home/tlytle/bin?
user3783243 21.06.2024 22:44

Используйте оператор ! после регулярного выражения для обработки строк, которые не соответствуют регулярному выражению. Например. /foo/!s/abc/def/ выполнит замену во всех строках, которые не содержат foo.

Barmar 21.06.2024 22:45

Используя GNU sed, сравните результаты <<<$'aabc\nxabc' sed -E 's/(aa){0}bc/ \&=& \\1 = "\1"/' и <<<$'aabc\nxabc' sed -E 's/(aa){0,}bc/ \&=& \\1 = "\1"/'.

potong 22.06.2024 10:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
116
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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}: группа, класс или отдельный символ с этим квантором описывают пустую строку (которая выполняется каждый раз, везде в строке и для всех строк). Это не отрицание. Имейте в виду, что выкройка — это описание, а не список пожеланий.

Другие вопросы по теме