Regex lookahead для 'not followed by' в grep

104

Я пытаюсь найти все экземпляры, за Ui\.которыми не следует Lineили даже просто букваL

Как правильно написать регулярное выражение для поиска всех экземпляров определенной строки, за которой НЕ следует другая строка?

Использование опережающих просмотров

grep "Ui\.(?!L)" *
bash: !L: event not found


grep "Ui\.(?!(Line))" *
nothing
Ли Куарелла
источник
5
Какие подвиды регулярных выражений - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Джонатан Леффлер
4
Кстати, «событие не найдено» исходит от использования раскрытия истории. Возможно, вы захотите отключить раскрытие истории, если никогда не используете его, а иногда хотите иметь возможность использовать восклицательный знак в своих интерактивных командах. set +o histexpandв Bash или set +HYMMV.
Tripleee
12
У меня также была проблема с расширением истории. Я думаю , что я решил его просто переключая на одинарные, так что оболочка не будет пытаться munge аргумента.
Coderer
@Coderer, который тоже решил мою проблему. Спасибо.
NHDaly

Ответы:

151

Отрицательный просмотр вперед, который вам нужен, требует более мощного инструмента, чем стандартный grep. Вам понадобится grep с поддержкой PCRE.

Если у вас есть GNU grep, текущая версия поддерживает параметры -Pили, --perl-regexpпосле чего вы можете использовать желаемое регулярное выражение.

Если у вас нет (достаточно последней версии) GNU grep, подумайте о приобретении ack.

Джонатан Леффлер
источник
37
Я почти уверен, что проблема в этом случае просто в том, что в bash вы должны использовать одинарные кавычки, а не двойные кавычки, поэтому он не будет рассматриваться !как специальный символ.
NHDaly
(см. ниже мой ответ, описывающий именно это.)
NHDaly
4
Подтвержденный, правильный ответ должен сочетать этот ответ и комментарий @NHDaly. Например, у меня работает эта команда: grep -P '^. * Contains ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf
3
Для тех , где -Pне поддерживается TRY результат трубопровода снова grep --invert-match, исключая: git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Не забудьте проголосовать за ответ @Vinicius Ottoni.
Daniel Sokolowski
@wangf Я использую Bash под Cygwin, и когда я перехожу на одинарные кавычки, я все еще получаю сообщение об ошибке «событие не найдено».
SSilk
41

Ответ на часть вашей проблемы здесь, и ack будет вести себя таким же образом: Ack и отрицательный просмотр вперед дают ошибки

Вы используете двойные кавычки для grep, что позволяет bash «интерпретировать !как команду раскрытия истории».

Вам нужно обернуть свой узор в ОДИНОЧНЫЕ ЦИТАТЫ: grep 'Ui\.(?!L)' *

Однако см . Ответ @JonathanLeffler, чтобы решить проблемы с негативным прогнозом в стандартном исполнении grep!

NHDaly
источник
Вы путаете функциональность расширения GNU grepс функциональностью стандарта grep, где стандартом grepявляется POSIX. То, что вы говорите, также верно - я запускаю Bash с отключенными варварствами C-оболочки (потому что, если бы я хотел оболочку C, я бы использовал ее, но я не хочу ее), поэтому на !меня это не влияет - но чтобы получить негативный взгляд вперед, нужен нестандартный grep.
Джонатан Леффлер
1
@JonathanLeffler, спасибо за разъяснения; Я думаю, что вы правы в том, что для устранения всех симптомов ОП требуется оба наших ответа. Спасибо.
NHDaly
11

Вы, вероятно, не можете выполнить стандартный отрицательный просмотр вперед с помощью grep, но обычно вы можете получить эквивалентное поведение, используя «обратный» переключатель '-v'. Используя это, вы можете создать регулярное выражение для дополнения того, что вы хотите сопоставить, а затем передать его через 2 greps.

Для рассматриваемого регулярного выражения вы можете сделать что-то вроде

grep 'Ui\.' * | grep -v 'Ui\.L'
Карел Тучек
источник
Это исключило бы больше вещей, больше экземпляров, если бы строка содержала Ui.Line и Ui без .Line
nafg
1
(Да, поэтому я не формулирую это строго. Это просто решает значительную часть сценариев, которые подводят людей к этой проблеме, не более того.)
Карел Тучек
4

Если вам нужно использовать реализацию регулярного выражения, которая не поддерживает отрицательный просмотр вперед, и вы не возражаете против сопоставления дополнительных символов *, тогда вы можете использовать классы отрицательных символов[^L] , чередование| и привязку конца строки$ .

В вашем случае grep 'Ui\.\([^L]\|$\)' *делает свою работу.

  • Ui\. совпадает с интересующей вас строкой

  • \([^L]\|$\)соответствует любому одиночному символу, кроме Lили соответствует концу строки: [^L]или $.

Если вы хотите исключить более одного символа, вам просто нужно добавить к нему больше чередования и отрицания. Найти aне следует bc:

grep 'a\(\([^b]\|$\)\|\(b\([^c]\|$\)\)\)' *

Что либо ( aза которым следует not bили за которым следует конец строки: athen [^b]или $), либо ( aза bкоторым следует либо not, cлибо следует конец строки: athen b, then [^c]или $.

Такое выражение становится довольно громоздким и подверженным ошибкам даже с короткой строкой. Вы могли бы написать что-нибудь для генерации выражений для вас, но, вероятно, было бы проще просто использовать реализацию регулярного выражения, которая поддерживает отрицательный просмотр вперед.

* Если ваша реализация поддерживает группы без захвата, вы можете избежать захвата дополнительных символов.

дугкозин
источник
1

Если ваш grep не поддерживает -P или --perl-regexp, и вы можете установить grep с поддержкой PCRE, например, "pcregrep", то ему не потребуются какие-либо параметры командной строки, такие как GNU grep, для принятия Perl-совместимого обычного выражения, вы просто бежите

pcregrep "Ui\.(?!Line)"

Вам не нужна другая вложенная группа для «Line», как в вашем примере «Ui. (?! (Line))» - достаточно внешней группы, как я показал выше.

Позвольте мне привести вам еще один пример поиска отрицательных утверждений: когда у вас есть список строк, возвращенный "ipset", каждая строка показывает количество пакетов в середине строки, и вам не нужны строки с нулевыми пакетами, вы просто бегать:

ipset list | pcregrep "packets(?! 0 )"

Если вам нравятся регулярные выражения, совместимые с perl, и у вас есть perl, но у вас нет pcregrep, или ваш grep не поддерживает --perl-regexp, вы можете использовать однострочные сценарии Perl, которые работают так же, как grep:

perl -e "while (<>) {if (/Ui\.(?!Lines)/){print;};}"

Perl принимает stdin так же, как grep, например

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Максим Масютин
источник