Regex lookahead dla „nie następuje po” w grep


104

Staram się grepować we wszystkich przypadkach, w których Ui\.nie następuje po Lineprostu literaL

Jaki jest właściwy sposób zapisania wyrażenia regularnego, aby znaleźć wszystkie wystąpienia określonego ciągu, a po nich NIE następuje inny ciąg?

Korzystanie z lookaheads

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


grep "Ui\.(?!(Line))" *
nothing

5
Który podgatunek wyrażenia regularnego - PCRE, ERE, BRE, grep, ed, sed, perl, python, Java, C, ...?
Jonathan Leffler

4
Nawiasem mówiąc, „wydarzenie nie znalezione” pochodzi z użycia rozszerzenia historii. Możesz chcieć wyłączyć rozwijanie historii, jeśli nigdy go nie używasz, a czasami chcesz mieć możliwość używania wykrzyknika w swoich interaktywnych poleceniach. set +o histexpandw Bash lub set +HYMMV.
tripleee

12
Miałem też problem z rozszerzeniem historii. Myślę , że rozwiązałem to po prostu przechodząc na pojedyncze cudzysłowy, aby powłoka nie próbowała ugryźć argumentu.
Coderer

@Coderer, który również rozwiązał mój problem. Dzięki.
NHDaly

Odpowiedzi:


151

Negatywne spojrzenie w przód, którego szukasz, wymaga potężniejszego narzędzia niż standardowe grep. Potrzebujesz grepa z obsługą PCRE.

Jeśli masz GNU grep, aktualna wersja obsługuje opcje -Plub --perl-regexpi możesz wtedy użyć żądanego wyrażenia regularnego.

Jeśli nie masz (wystarczająco aktualnej wersji) GNU grep, rozważ możliwość pobrania ack.


37
Jestem prawie pewien, że problem w tym przypadku polega na tym, że w bashu należy używać pojedynczych cudzysłowów, a nie podwójnych cudzysłowów, więc nie będzie to traktowane !jako znak specjalny.
NHDaly,

(patrz poniżej, gdzie moja odpowiedź dokładnie to opisuje.)
NHDaly

4
Zweryfikowana, poprawna odpowiedź powinna być połączeniem tej odpowiedzi i komentarza @ NHDaly. Na przykład to polecenie działa dla mnie: grep -P '^. * Zawiera ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf

3
W przypadku tych, dla których -Pnie jest to obsługiwane, spróbuj ponownie uzyskać wynik potokowania grep --invert-match, np git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. : . Pamiętaj, aby zagłosować za odpowiedzią @Vinicius Ottoni.
Daniel Sokolowski

@wangf Używam Bash pod Cygwin i kiedy zmieniam na pojedyncze cudzysłowy, nadal otrzymuję błąd „nie znaleziono zdarzenia”.
SSilk

41

Odpowiedź na część twojego problemu jest tutaj, a potwierdzenie zachowałoby się w ten sam sposób: Potwierdzenie i negatywne spojrzenie w przód z błędami

Używasz podwójnych cudzysłowów dla grep, co pozwala bashowi "interpretować !jako polecenie rozszerzania historii".

Musisz zawinąć swój wzór w POJEDYNCZE CYTATY: grep 'Ui\.(?!L)' *

Jednak zobacz odpowiedź @ JonathanLeffler, aby rozwiązać problemy z negatywnym wyprzedzeniem w standardzie grep!


Mylisz funkcjonalność rozszerzeń GNU grepz funkcjonalnością standardu grep, gdzie standardem dla grepjest POSIX. To, co mówisz, jest również prawdą - uruchamiam Bash z wyłączonym barbarzyństwem powłoki C (bo gdybym chciał powłoki C, użyłbym jednej, ale nie chcę), więc !to nie wpływa na mnie - ale aby uzyskać negatywne wyprzedzenia, potrzebujesz niestandardowych grep.
Jonathan Leffler

1
@JonathanLeffler, dzięki za wyjaśnienie; Myślę, że masz rację, że obie nasze odpowiedzi wymagają rozwiązania wszystkich objawów PO. Dzięki.
NHDaly

11

Prawdopodobnie nie możesz wykonać standardowego negatywnego lookahead używając grep, ale zwykle powinieneś być w stanie uzyskać równoważne zachowanie używając "odwrotnego" przełącznika '-v'. Korzystając z tego, możesz skonstruować wyrażenie regularne jako uzupełnienie tego, co chcesz dopasować, a następnie przepuścić je przez 2 greps.

W przypadku danego wyrażenia regularnego możesz zrobić coś takiego jak

grep 'Ui\.' * | grep -v 'Ui\.L'

Wykluczyłoby to więcej rzeczy, więcej instancji, gdyby wiersz zawierał Ui.Line i Ui bez
.Line

1
(Tak, dlatego nie formułuję tego ściśle. To po prostu rozwiązuje znaczną część scenariuszy, które
kierują

4

Jeśli potrzebujesz implementacji wyrażenia regularnego, która nie obsługuje ujemnych lookaheadów i nie masz nic przeciwko dopasowaniu dodatkowych znaków *, możesz użyć zanegowanych klas znaków[^L] , alternacji| i zakotwiczenia końca łańcucha$ .

W twoim przypadku grep 'Ui\.\([^L]\|$\)' *spełnia swoje zadanie.

  • Ui\. pasuje do ciągu, który Cię interesuje

  • \([^L]\|$\)dopasowuje dowolny pojedynczy znak inny niż Llub pasuje do końca wiersza: [^L]lub $.

Jeśli chcesz wykluczyć więcej niż jeden znak, wystarczy rzucić na niego więcej zmian i negacji. Aby znaleźć anie następuje bc:

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

Który jest albo ( apo którym następuje not blub koniec wiersza: athen [^b]lub $) albo ( apo bktórym następuje albo nie, calbo koniec wiersza: athen b, then [^c]lub $.

Tego rodzaju wyrażenie staje się dość nieporęczne i podatne na błędy nawet przy krótkim ciągu. Mógłbyś napisać coś, co wygeneruje dla ciebie wyrażenia, ale prawdopodobnie byłoby łatwiej po prostu użyć implementacji regex, która obsługuje negatywne lookaheads.

* Jeśli Twoja implementacja obsługuje grupy bez przechwytywania , możesz uniknąć przechwytywania dodatkowych znaków.


1

Jeśli twój grep nie obsługuje -P lub --perl-regexp i możesz zainstalować grep z obsługą PCRE, np. "Pcregrep", to nie będzie potrzebował żadnych opcji wiersza poleceń, takich jak GNU grep, aby zaakceptować zwykły zgodny z Perl wyrażenia, po prostu uciekasz

pcregrep "Ui\.(?!Line)"

Nie potrzebujesz kolejnej grupy zagnieżdżonej dla „Linii”, jak w Twoim przykładzie „Ui. (?! (Linia))” - zewnętrzna grupa jest wystarczająca, jak pokazałem powyżej.

Pozwólcie, że podam inny przykład szukania negatywnych twierdzeń: kiedy masz listę wierszy, zwracaną przez "ipset", każda linia pokazuje liczbę pakietów w środku linii i nie potrzebujesz linii z zerowymi pakietami, po prostu biegać:

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

Jeśli lubisz wyrażenia regularne kompatybilne z perlem i masz perl, ale nie masz programu pcregrep lub twój grep nie obsługuje --perl-regexp, możesz użyć jednowierszowych skryptów perl, które działają tak samo jak grep:

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

Perl akceptuje stdin w taki sam sposób jak grep, np

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.