Grep: Das Sternchen (*) funktioniert nicht immer

11

Wenn ich ein Dokument greife, das Folgendes enthält:

ThisExampleString

... für den Ausdruck This*Stringoder *Stringwird nichts zurückgegeben. Allerdings This*gibt die obigen Zeilen wie erwartet.

Ob der Ausdruck in Anführungszeichen steht, spielt keine Rolle.

Ich dachte, das Sternchen zeigt eine beliebige Anzahl unbekannter Zeichen an? Warum funktioniert es nur, wenn es am Anfang des Ausdrucks steht? Was verwende ich anstelle der Ausdrücke This*Stringund, wenn dies beabsichtigtes Verhalten ist *String?

Trae
quelle
denn so funktioniert Regex nicht ... (insbesondere: * != any number of unknown charactersLesen Sie das Dokument.)
njzk2

Antworten:

18

Ein Sternchen in regulären Ausdrücken bedeutet "mindestens 0 Mal mit dem vorhergehenden Element übereinstimmen".

In Ihrem speziellen Fall mit grep 'This*String' file.txtversuchen Sie zu sagen: "Hey, grep, passen Sie mir das Wort an Thi, gefolgt von snull oder mehr Kleinbuchstaben , gefolgt von dem Wort String". Der Kleinbuchstabe sist nirgends zu finden Example, daher ignoriert grep ihn ThisExampleString.

Im Fall von grep '*String' file.txtsagen Sie "grep, passen Sie mir die leere Zeichenfolge - buchstäblich nichts - vor dem Wort String". Natürlich soll das nicht ThisExampleStringso gelesen werden. (Es gibt andere mögliche Bedeutungen - Sie können dies mit und ohne -EFlagge versuchen - aber keine der Bedeutungen entspricht in etwa dem, was Sie hier wirklich wollen.)

Da .wir wissen, dass dies "jedes einzelne Zeichen" bedeutet, können wir Folgendes tun : grep 'This.*String' file.txt. Jetzt liest der Befehl grep es richtig: Thisgefolgt von einem beliebigen Zeichen (stellen Sie es sich als Auswahl von ASCII-Zeichen vor), das beliebig oft wiederholt wird, gefolgt von String.

Sergiy Kolodyazhnyy
quelle
6
In Bash (und den meisten Unix-Shells) *handelt es sich um ein Sonderzeichen, das beispielsweise wie folgt zitiert oder maskiert werden sollte: grep 'This*String' file.txtoder dies: grep This\*String file.txtum nicht von unerwarteten Ergebnissen überrascht zu werden.
Pabouk
2
@ Pabouk in Muscheln, das *ist ein Platzhalter. In grep *ist ein Operator für reguläre Ausdrücke. Siehe unix.stackexchange.com/q/57957/70524
muru
11
pabouk ist richtig, die Dateinamenerweiterung findet statt, bevor der Befehl ausgeführt wird; vergleiche strace grep .* file.txt |& head -n 1 und strace grep '.*' file.txt |& head -n 1. Funktioniert auch tatsächlich grepmit jedem Unicode-Zeichen (z. B. echo -ne ⇏ | grep ⇏Ausgängen )
kos
1
@Serg: du hast hier ein hohes Ansehen, also dachte ich, dass du sofort merkst, was ich meine. Das OP hat die Frage- Bash markiert, daher gehe ich davon aus, dass die besprochenen Befehle von interpretiert werden bash. Dies bedeutet, dass zuerst bashdie Sonderzeichen interpretiert werden und erst nach allen durchgeführten Erweiterungen die Parameter an den erzeugten Prozess übergeben werden. ----- Zum Beispiel dieses Befehl in heftigen Schlag: grep This.\*String file.txtwird Laichen /bin/grepmit diesen Parametern 0: grep, 1: This.*String2: file.txt. Beachten Sie, dass Bash den Backslash entfernt hat und das ursprünglich entkommene *Wort buchstäblich übergeben wurde.
Pabouk
7
Das Lustige (und für die Fehlerbehebung ziemlich böse :) ist, dass Ihre Befehle grep This.*String file.txtnormalerweise funktionieren, da höchstwahrscheinlich keine Datei vorhanden ist, die dem Shell-Platzhalterausdruck entspricht This.*String. In einem solchen Fall übergibt Bash das Argument standardmäßig wörtlich einschließlich *.
Pabouk
8

Das *Metazeichen in BRE 1 s, ERE 1 s und PCRE 1 s entspricht 0 oder mehr Vorkommen des zuvor gruppierten Musters (wenn ein gruppiertes Muster vor dem *Metazeichen steht), 0 oder mehr Vorkommen der vorherigen Zeichenklasse (wenn eine Zeichenklasse vorhanden ist) vor dem *Metazeichen) oder 0 oder mehr Vorkommen des vorherigen Zeichens (wenn weder ein gruppiertes Muster noch eine Zeichenklasse vor dem *Metazeichen steht);

Dies bedeutet, dass im This*StringMuster als *Metazeichen, dem weder ein gruppiertes Muster noch eine Zeichenklasse vorangestellt ist, das *Metazeichen mit 0 oder mehr Vorkommen des vorherigen Zeichens (in diesem Fall des sZeichens) übereinstimmt :

% cat infile               
ThisExampleString
ThisString
ThissString
% grep 'This*String' infile
ThisString
ThissString

Um 0 oder mehr Vorkommen eines Zeichens zuzuordnen, möchten Sie 0 oder mehr Vorkommen des .Metazeichens zuordnen, das einem beliebigen Zeichen entspricht:

% cat infile               
ThisExampleString
% grep 'This.*String' infile
ThisExampleString

Das *Metazeichen in BREs und EREs ist immer "gierig", dh es stimmt mit der längsten Übereinstimmung überein:

% cat infile
ThisExampleStringIsAString
% grep -o 'This.*String' infile
ThisExampleStringIsAString

Dies ist möglicherweise nicht das gewünschte Verhalten. Falls dies nicht der Fall ist, können Sie die grepPCRE-Engine (mithilfe der -POption) einschalten und das ?Metazeichen anhängen , das, wenn es nach dem *und den +Metazeichen gesetzt wird, die Gier ändert:

% cat infile
ThisExampleStringIsAString
% grep -Po 'This.*?String' infile
ThisExampleString

1: Grundlegende reguläre Ausdrücke, erweiterte reguläre Ausdrücke und Perl-kompatible reguläre Ausdrücke

kos
quelle
Vielen Dank für die sehr informative Antwort. Ich habe jedoch eine andere Antwort gewählt, weil sie kürzer und leichter zu verstehen war. +1 für so viele Details.
Trae
@Trae Gern geschehen. Es ist in Ordnung, ich stimme zu, dass dies vielleicht zu komplex war und zu viele Annahmen für jemanden gemacht hat, der mit dem Thema nicht allzu vertraut ist.
Kos
4

Eine der hier gefundenen Erklärungen Link :

Sternchen " *" bedeutet in regulären Ausdrücken nicht dasselbe wie beim Platzhalter; Dies ist ein Modifikator, der für das vorhergehende Einzelzeichen oder einen Ausdruck wie [0-9] gilt. Ein Sternchen entspricht null oder mehr der vorangegangenen Punkte. Somit [A-Z]*entspricht eine beliebige Anzahl von Großbuchstaben, einschließlich none, während für [A-Z][A-Z]*einen oder mehr Großbuchstaben.

Eizellen
quelle
1

*hat eine besondere Bedeutung sowohl als Shell- Globbing- Zeichen ("Wildcard") als auch als Metazeichen für reguläre Ausdrücke . Sie müssen beides berücksichtigen. Wenn Sie jedoch Ihren regulären Ausdruck zitieren, können Sie verhindern, dass die Shell ihn speziell behandelt, und sicherstellen, dass er ihn unverändert weitergibt grep. Obwohl Art ähnlich konzeptionell, welche *Mittel zur Schale ist ganz anders aus , was es bedeutet , zu grep.

Zuerst wird die Shell *als Platzhalter behandelt .

Du sagtest:

Ob der Ausdruck in Anführungszeichen steht, spielt keine Rolle.

Dies hängt davon ab, welche Dateien in dem Verzeichnis vorhanden sind, in dem Sie sich gerade befinden, wenn Sie den Befehl ausführen. Bei Mustern, die das Verzeichnistrennzeichen enthalten /, kann es davon abhängen, welche Dateien auf Ihrem gesamten System vorhanden sind. Sie sollten immer zitieren reguläre Ausdrücke für grep--und einfache Anführungszeichen in der Regel Besten-- sind , es sei denn Sie sicher sind , bist du in Ordnung mit den neun Arten von potentiell überraschend Transformationen führt die Shell sonst vor dem Ausführen - grepBefehl.

Wenn die Shell auf ein *Zeichen stößt , das nicht in Anführungszeichen steht , bedeutet dies "null oder mehr Zeichen" und ersetzt das Wort, das es enthält, durch eine Liste von Dateinamen, die dem Muster entsprechen. (Dateinamen , die mit beginnen , .sind ausgeschlossen - es sei denn , Ihr Muster selbst beginnt mit . oder . Sie haben konfiguriert Shell , sie trotzdem schließen) Dies ist bekannt als Globbing --und auch durch den Namen Dateinamen Expansion und Pfadname Expansion .

Der Effekt mit grepwird normalerweise sein, dass der erste übereinstimmende Dateiname als regulärer Ausdruck verwendet wird - auch wenn es für einen menschlichen Leser ziemlich offensichtlich ist, dass er nicht als regulärer Ausdruck gemeint ist -, während alle anderen Dateinamen automatisch von Ihrem aufgelistet werden glob werden als Dateien verwendet, in denen nach Übereinstimmungen gesucht werden soll. (Sie sehen die Liste nicht - sie wird undurchsichtig weitergegeben grep.) Sie möchten praktisch nie, dass dies geschieht.

Der Grund, warum dies manchmal kein Problem ist - und in Ihrem speziellen Fall zumindest bisher nicht - ist, dass *dies in Ruhe gelassen wird, wenn alle der folgenden Aussagen zutreffen :

  1. Es gab keine Dateien, deren Namen übereinstimmten. ... oder Sie haben das Globbing in Ihrer Shell deaktiviert, normalerweise mit set -foder gleichwertig set -o noglob. Aber das ist ungewöhnlich und Sie würden wahrscheinlich wissen, dass Sie es getan haben.

  2. Sie verwenden eine Shell, deren Standardverhalten darin besteht, sie in *Ruhe zu lassen , wenn keine übereinstimmenden Dateinamen vorhanden sind. Dies ist in Bash der Fall, das Sie wahrscheinlich verwenden, jedoch nicht in allen Bourne-Shells. (Das Standardverhalten in der beliebten Shell Zsh besteht beispielsweise darin, dass Globs entweder (a) erweitern oder (b) einen Fehler erzeugen.) ... oder Sie haben dieses Verhalten Ihrer Shell geändert - wie dies gemacht wird, ist unterschiedlich über Muscheln.

  3. Sie haben Ihrer Shell nicht anderweitig mitgeteilt, dass Globs durch nichts ersetzt werden sollen, wenn keine übereinstimmenden Dateien vorhanden sind, und dass in dieser Situation keine Fehlermeldung angezeigt wird . In Bash wäre dies durch Aktivieren der Optionnullglob oder failglob Shell geschehen .

Sie können sich manchmal auf # 2 und # 3 verlassen, aber Sie können sich selten auf # 1 verlassen. Ein grepBefehl mit einem nicht zitierten Muster, der jetzt funktioniert, funktioniert möglicherweise nicht mehr, wenn Sie andere Dateien haben oder wenn Sie ihn von einem anderen Ort aus ausführen. Zitieren Sie Ihren regulären Ausdruck und das Problem verschwindet.

Dann wird der grepBefehl *als Quantifizierer behandelt .

Die anderen Antworten - wie die von Sergiy Kolodyazhnyy und von kos - sprechen diesen Aspekt dieser Frage ebenfalls auf etwas andere Weise an. Deshalb ermutige ich diejenigen, die sie noch nicht gelesen haben, dies entweder vor oder nach dem Lesen des Restes dieser Antwort zu tun.

Unter der Annahme, *dass es zu grep kommt - was das Zitieren sicherstellen sollte - bedeutet grepdies, dass das Element, das davor steht, beliebig oft vorkommen kann , anstatt genau einmal auftreten zu müssen . Es könnte immer noch einmal auftreten. Oder es könnte überhaupt nicht vorhanden sein. Oder es könnte wiederholt werden. Text, der zu einer dieser Möglichkeiten passt, wird abgeglichen.

Was meine ich mit "Artikel"?

  • Ein einzelnes Zeichen . Da bFindet ein Zeichen b, b*entspricht null oder mehr bs, also ab*cpasst ac, abc, abbc, abbbcetc.

    Da in ähnlicher Weise .jedes Zeichen , .*entspricht null oder mehr Zeichen 1 , so a.*cStreichhölzer ac, akc, ahjglhdfjkdlgjdfkshlgc, selbst acccccchjckhcc, usw. Oder

  • Eine Charakterklasse . Da [xy]Streichhölzer xoder y, [xy]*entspricht null oder mehr Zeichen , wo jeder entweder ist xoder ydamit p[xy]*qübereinstimmt pq, pxq, pyq, pxxq, pxyq, pyxq, pyyq, pxxxq, pxxyq, usw.

    Dies gilt auch für Formen Kurzschrift von Zeichenklassen wie \w, \W, \s, und \S. Da \wmit jedem Wortzeichen \w*übereinstimmt , entspricht es null oder mehr Wortzeichen. Oder

  • Eine Gruppe . Da \(bar\)Streichhölzer bar, \(bar\)*Streichhölzer null oder mehr bars, also foo\(bar\)*bazpasst foobaz, foobarbaz, foobarbarbaz, foobarbarbarbazetc.

    Behandelt Ihren regulären Ausdruck mit den Optionen -Eoder als ERE bzw. PCRE und nicht als BRE , und dann werden Gruppen von anstelle von umgeben , sodass Sie stattdessen anstelle von und anstelle von verwenden .-Pgrep( )\( \)(bar)\(bar\)foo(bar)bazfoo\(bar\)baz

man grepbietet am Ende eine einigermaßen leicht zugängliche Erklärung der BRE- und ERE-Syntax sowie eine Auflistung aller grepam Anfang akzeptierten Befehlszeilenoptionen . Ich empfehle diese Handbuchseite als Ressource sowie die GNU Grep-Dokumentation und diese Tutorial- / Referenzseite (auf die ich oben mit einer Reihe von Seiten verlinkt habe).

Zum Testen und Lernen grepempfehle ich, es mit einem Muster, aber ohne Dateinamen aufzurufen. Dann nimmt es Eingaben von Ihrem Terminal entgegen. Zeilen eingeben; Die Zeilen, die an Sie zurückgesendet werden, enthalten Text, mit dem Ihr Muster übereinstimmt. Drücken Sie zum Beenden Ctrl+ Dam Anfang einer Zeile, die das Ende der Eingabe signalisiert. (Oder Sie können wie bei den meisten Befehlszeilenprogrammen Ctrl+ drücken C.) Zum Beispiel:

grep 'This.*String'

Wenn Sie das --colorFlag verwenden, grepwerden die spezifischen Teile Ihrer Zeilen hervorgehoben, die Ihrem regulären Ausdruck entsprechen. Dies ist sehr nützlich, um herauszufinden, was ein regulärer Ausdruck bewirkt, und um zu finden, wonach Sie suchen, sobald Sie dies tun. Standardmäßig verfügen Ubuntu-Benutzer über einen Bash-Alias, der grep --color=autozur Ausführung führt - was für diesen Zweck ausreichend ist -, wenn Sie grepüber die Befehlszeile ausgeführt werden, sodass Sie wahrscheinlich nicht einmal --colormanuell übergeben müssen.

1 Daher bedeutet .*in einem regulären Ausdruck, was *in einem Shell-Glob bedeutet. Der Unterschied besteht jedoch darin, dass grepautomatisch Zeilen gedruckt werden, die Ihre Übereinstimmung an einer beliebigen Stelle enthalten. Daher ist es normalerweise nicht erforderlich, sie .*am Anfang oder Ende eines regulären Ausdrucks zu haben.

Eliah Kagan
quelle