Regex Lookahead für 'nicht gefolgt von' in grep


102

Ich versuche, nach allen Fällen zu suchen, in denen Ui\.nicht Lineoder nur der Brief folgtL

Was ist der richtige Weg, um einen regulären Ausdruck zu schreiben, um alle Instanzen einer bestimmten Zeichenfolge zu finden, gefolgt von einer anderen Zeichenfolge?

Lookaheads verwenden

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


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

5
Welche Unterarten von Regex - PCRE, ERE, BRE, Grep, Ed, Sed, Perl, Python, Java, C, ...?
Jonathan Leffler

4
Abgesehen davon stammt das "Ereignis nicht gefunden" aus der Verwendung der Verlaufserweiterung. Möglicherweise möchten Sie die Verlaufserweiterung deaktivieren, wenn Sie sie nie verwenden, und manchmal möchten Sie in Ihren interaktiven Befehlen ein Ausrufezeichen verwenden. set +o histexpandin Bash oder set +HYMMV.
Tripleee

12
Ich hatte auch das Problem der Erweiterung der Geschichte. Ich glaube, ich habe es einfach gelöst, indem ich zu einfachen Anführungszeichen gewechselt bin, damit die Shell nicht versucht, das Argument zu verwechseln.
Coderer

@Coderer, der auch mein Problem gelöst hat. Vielen Dank.
NHDaly

Antworten:


150

Negativer Lookahead, nach dem Sie suchen, erfordert ein leistungsfähigeres Werkzeug als der Standard grep. Sie benötigen einen PCRE-fähigen grep.

Wenn Sie GNU haben grep, unterstützt die aktuelle Version Optionen -Poder --perl-regexpund Sie können dann den gewünschten regulären Ausdruck verwenden.

Wenn Sie keine (ausreichend aktuelle) Version von GNU haben grep, sollten Sie in Betracht ziehen, diese zu erwerben ack.


37
Ich bin mir ziemlich sicher, dass das Problem in diesem Fall nur darin besteht, dass Sie in bash einfache Anführungszeichen und keine doppelten Anführungszeichen verwenden sollten, damit es nicht !als Sonderzeichen behandelt wird .
NHDaly

(Siehe unten für meine Antwort, die genau das beschreibt.)
NHDaly

4
Eine verifizierte, korrekte Antwort sollte diese Antwort mit dem Kommentar von @ NHDaly kombinieren. Zum Beispiel funktioniert dieser Befehl für mich: grep -P '^. * Enthält ((?! But_not_this).) * $' * .Log. *> "D: \ temp \ result.out"
wangf

3
Für diejenigen, die -Pnicht unterstützt werden, versuchen Sie es erneut mit dem Rohrleitungsergebnis grep --invert-match, z git log --diff-filter=D --summary | grep -E 'delete.*? src' | grep -E --invert-match 'xml'. Stellen Sie sicher, dass Sie die Antwort von @Vinicius Ottoni positiv bewerten.
Daniel Sokolowski

@wangf Ich verwende Bash unter Cygwin und wenn ich zu einfachen Anführungszeichen wechsle, wird immer noch der Fehler "Ereignis nicht gefunden" angezeigt.
SSilk

39

Die Antwort auf einen Teil Ihres Problems ist hier, und ack würde sich genauso verhalten: Ack & negativer Lookahead geben Fehler

Sie verwenden doppelte Anführungszeichen für grep, wodurch bash " !als Befehl zum Erweitern des Verlaufs interpretieren " kann.

Sie müssen Ihr Muster in EINZELZITATE einwickeln: grep 'Ui\.(?!L)' *

Allerdings sehen @ JonathanLeffler Antwort auf die Probleme mit negativen Lookaheads in Standard - Adresse grep!


Sie verwechseln die Erweiterungsfunktionalität von GNU grepmit der Funktionalität von Standard grep, wobei der Standard für grepPOSIX ist. Was Sie sagen, ist auch wahr - ich führe Bash mit deaktivierten C-Shell-Barbaren aus (denn wenn ich eine C-Shell wollte, würde ich eine verwenden, aber ich möchte keine), damit !mich das Zeug nicht betrifft - Aber um negative Lookaheads zu erhalten, benötigen Sie einen Nicht-Standard grep.
Jonathan Leffler

1
@ JonathanLeffler, danke für die Klarstellung; Ich denke, Sie haben Recht, dass unsere beiden Antworten erforderlich sind, um alle Symptome des OP anzugehen. Vielen Dank.
NHDaly

10

Sie können wahrscheinlich keine negativen Standard-Lookaheads mit grep ausführen, aber normalerweise sollten Sie in der Lage sein, ein gleichwertiges Verhalten mit dem "inversen" Schalter '-v' zu erzielen. Auf diese Weise können Sie einen regulären Ausdruck für die Ergänzung dessen erstellen, was Sie anpassen möchten, und ihn dann durch 2 Greps leiten.

Für den fraglichen regulären Ausdruck könnten Sie so etwas tun

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

Das würde mehr Dinge ausschließen, mehr Instanz, wenn die Zeile Ui.Line und Ui ohne .Line enthält
nafg

1
(Ja, deshalb formuliere ich es nicht streng. Dies löst einfach einen wesentlichen Teil der Szenarien, die die Menschen zu diesem Problem führen, nichts weiter.)
Karel Tucek

4

Wenn Sie eine Regex-Implementierung verwenden müssen, die keine negativen Lookaheads unterstützt, und es Ihnen nichts ausmacht, zusätzliche Zeichen * zuzuordnen, können Sie negierte Zeichenklassen[^L] , Alternation| und das Ende des String-Ankers verwenden$ .

In deinem Fall grep 'Ui\.\([^L]\|$\)' *macht der Job.

  • Ui\. entspricht der Zeichenfolge, an der Sie interessiert sind

  • \([^L]\|$\)Entspricht einem anderen Zeichen als Loder dem Zeilenende: [^L]oder $.

Wenn Sie mehr als nur ein Zeichen ausschließen möchten, müssen Sie nur mehr Abwechslung und Negation darauf werfen. Zu finden a, gefolgt von bc:

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

Welches ist entweder ( agefolgt von nicht boder gefolgt vom Ende der Zeile: adann [^b]oder $) oder ( agefolgt von bdem entweder gefolgt von nicht coder gefolgt von dem Ende der Zeile: adann b, dann [^c]oder $.

Diese Art von Ausdruck wird selbst mit einer kurzen Zeichenfolge ziemlich unhandlich und fehleranfällig. Sie könnten etwas schreiben, um die Ausdrücke für Sie zu generieren, aber es wäre wahrscheinlich einfacher, nur eine Regex-Implementierung zu verwenden, die negative Lookaheads unterstützt.

* Wenn Ihre Implementierung nicht erfassende Gruppen unterstützt , können Sie das Erfassen zusätzlicher Zeichen vermeiden.


1

Wenn Ihr grep -P oder --perl-regexp nicht unterstützt und Sie PCRE-fähiges grep installieren können, z. B. "pcregrep", benötigt es keine Befehlszeilenoptionen wie GNU grep, um Perl-kompatibles reguläres zu akzeptieren Ausdrücke, du rennst einfach

pcregrep "Ui\.(?!Line)"

Sie benötigen keine weitere verschachtelte Gruppe für "Line" wie in Ihrem Beispiel "Ui. (?! (Line))" - die äußere Gruppe ist ausreichend, wie ich oben gezeigt habe.

Lassen Sie mich Ihnen ein weiteres Beispiel für negative Aussagen geben: Wenn Sie eine Liste von Zeilen haben, die von "ipset" zurückgegeben werden, wobei jede Zeile die Anzahl der Pakete in der Mitte der Zeile anzeigt und Sie keine Zeilen mit null Paketen benötigen, Sie nur Lauf:

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

Wenn Sie Perl-kompatible reguläre Ausdrücke mögen und Perl haben, aber kein pcregrep haben oder Ihr grep --perl-regexp nicht unterstützt, können Sie einzeilige Perl-Skripte verwenden, die genauso funktionieren wie grep:

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

Perl akzeptiert stdin genauso wie grep, z

ipset list | perl -e "while (<>) {if (/packets(?! 0 )/){print;};}"
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.