Es scheint, dass ich grep / egrep missbrauche.

Ich habe versucht, in mehreren Zeilen nach Zeichenfolgen zu suchen, und konnte keine Übereinstimmung finden, obwohl ich weiß, dass das, wonach ich suche, übereinstimmen sollte. Ursprünglich dachte ich, dass meine regulären Ausdrücke falsch waren, aber ich habe schließlich gelesen, dass diese übereinstimmen Werkzeuge arbeiten pro Zeile (auch meine regulären Ausdrücke waren so trivial, dass es nicht das Problem sein konnte).

Welches Werkzeug würde man also verwenden, um Muster über mehrere Zeilen hinweg zu suchen?

Kommentare

  • mögliches Duplikat von Mehrzeilige Musterübereinstimmung mit sed, awk oder grep
  • @CiroSantilli – Ich glaube nicht, dass dieses Q und das, mit dem Sie verknüpft sind, Duplikate sind. Das andere Q fragt, wie Sie ‚ eine mehrzeilige Musterübereinstimmung durchführen würden (dh welches Tool sollte / kann ich verwenden, um dies zu tun), während dieser fragt, wie dies mit grep zu tun ist. Sie sind eng miteinander verbunden, aber keine Dups, IMO.
  • @sim diese Fälle sind schwer zu entscheiden: Ich kann Ihren Standpunkt sehen. Ich denke, dieser spezielle Fall ist besser als Duplikat, weil Der Benutzer sagte "grep" und schlug das Verb “ vor, um “ zu grep. einschließlich akzeptiert, verwenden Sie ‚ kein grep.
  • Es gibt keinen Hinweis darauf, dass hier ein mehrzeiliger regulärer Ausdruck erforderlich ist. Bitte zeigen Sie ein aktuelles Beispiel mit Eingabedaten und erwarteten Ausgabedaten sowie Ihren bisherigen Bemühungen.

Antwort

Hier „sa sed eine, die Ihnen grep -ähnliches Verhalten über mehrere Zeilen hinweg verleiht:

sed -n "/foo/{:start /bar/!{N;b start};/your_regex/p}" your_file 

Funktionsweise

  • -n unterdrückt das Standardverhalten beim Drucken jeder Zeile
  • /foo/{} weist sie an, foo und tun Sie, was in den Kringeln zu den übereinstimmenden Linien kommt. Ersetzen Sie foo durch den Anfangsteil des Musters.
  • :start ist eine Verzweigungsbezeichnung, die uns hilft, die Schleife fortzusetzen, bis wir das Ende unserer Regex gefunden haben.
  • /bar/!{} führt aus, was in den Kringeln steht Die Zeilen, die nicht mit bar übereinstimmen. Ersetzen Sie mit dem Endteil des Musters.
  • N hängt die nächste Zeile an den aktiven Puffer an ( nennt dies den Musterraum)
  • b start verzweigt bedingungslos zum von uns erstellten start -Label früher, um die nächste Zeile anzuhängen, solange der Musterbereich nicht bar enthält.
  • /your_regex/p druckt den Musterbereich, wenn er mit your_regex übereinstimmt. Sie sollten your_regex durch den gesamten Ausdruck ersetzen, den Sie über mehrere Zeilen hinweg abgleichen möchten.

Kommentare

  • +1 Dies zum Toolikt hinzufügen! Danke.
  • Hinweis: Unter MacOS gibt dies sed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
  • sed: unterminated { Fehler
  • @Nomaed Hier im Dunkeln aufgenommen, aber enthält Ihre Regex zufällig “ {“ Zeichen? Wenn ja, müssen Sie ‚ einen Backslash-Escape-Vorgang ausführen.
  • @Nomaed Es scheint, dass dies mit die Unterschiede zwischen sed Implementierungen. Ich habe versucht, den Empfehlungen in dieser Antwort zu folgen, um das obige Skript standardkonform zu machen, aber es hat mir gesagt, dass “ start “ undefiniert ist Etikette. Ich bin mir also ‚ nicht sicher, ob dies auf standardkonforme Weise möglich ist. Wenn Sie es schaffen, können Sie meine Antwort jederzeit bearbeiten.

Antwort

Ich verwende im Allgemeinen ein Tool genannt pcregrep, das in den meisten Linux-Versionen mit yum oder apt installiert werden kann.

Zum Beispiel.

Angenommen, Sie haben eine Datei mit dem Namen testfile mit Inhalt

abc blah blah blah def blah blah blah 

Sie können den folgenden Befehl ausführen:

$ pcregrep -M "abc.*(\n|.)*def" testfile 

, um einen Mustervergleich über mehrere Zeilen hinweg durchzuführen.

Sie können dasselbe auch mit sed tun.

$ sed -e "/abc/,/def/!d" testfile 

Kommentare

  • Dieser sed Vorschlag wird übersprungen Die Zeile, in der def gefunden wird

Antwort

Einfach Ein normaler grep, der den Parameter Perl-regexp P unterstützt, erledigt diesen Job.

$ echo "abc blah blah blah def blah blah blah" | grep -oPz "(?s)abc.*?def" abc blah blah blah def 

(?s) heißt DOTALL-Modifikator, wodurch der Punkt in Ihrer Regex nicht nur den Zeichen, sondern auch den Zeilenumbrüchen entspricht.

Kommentare

  • Wenn ich diese Lösung versuche, endet die Ausgabe nicht bei ‚ def ‚ geht aber zum Ende der Datei ‚ blah ‚
  • vielleicht Ihr grep unterstützt nicht die Option -P
  • Dies war die einzige, die für mich funktioniert hat – habe alle sed -Vorschläge ausprobiert. ‚ ging jedoch nicht so weit, grep-Alternativen zu installieren.
  • $ grep --version: grep (GNU grep) 3.1 im Windows Git Bash hat eine Option -P, --perl-regexp, aber (?s) tut dies nicht ‚ scheint dort nicht zu funktionieren. Es wird immer noch nur die erste Zeile angezeigt. Das gleiche Muster mit der gleichen Testzeichenfolge funktioniert auf regex101.com . Gibt es eine Alternative im Git Bash? sed? (sed (GNU sed) 4.8 hier)
  • Wissen Sie, wie Sie der Ausgabe Kontext hinzufügen? grep -1 funktioniert hier nicht ‚.

Antwort

Hier ist ein einfacherer Ansatz mit Perl:

perl -e "$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m" file 

oder (da JosephR die sed route , ich werde schamlos seinen Vorschlag )

perl -n000e "print $& while /^foo.*\nbar.*\n/mg" file 

### Erläuterung

$f=join("",<>);: Dies liest die gesamte Datei und speichert ihren Inhalt (Zeilenumbrüche und alle) in der Variablen $f. Wir versuchen dann, foo\nbar.*\n zu finden und drucken es aus, wenn es übereinstimmt (die spezielle Variable $& enthält die zuletzt gefundene Übereinstimmung). Die ///m wird benötigt, damit der reguläre Ausdruck über Zeilenumbrüche hinweg übereinstimmt.

Die -0 legt das Trennzeichen für den Eingabedatensatz fest. Wenn Sie dies auf 00 setzen, wird der „Absatzmodus“ aktiviert, in dem Perl aufeinanderfolgende Zeilenumbrüche (\n\n) als Datensatztrennzeichen verwendet. In Fällen, in denen keine aufeinanderfolgenden Zeilenumbrüche vorhanden sind, wird die gesamte Datei auf einmal gelesen (geschlürft).

### Warnung: Bei großen Dateien nicht ausführen, wird sie geladen die gesamte Datei in den Speicher und das kann ein Problem sein.

Kommentare

  • Ich ‚ t Ich weiß viel über Perl, aber würde es ‚ nicht my $f=join("",<>); sein, genau genommen?
  • @Sapphire_Brick Wenn Sie sich im strengen Modus befinden (use strict;). ‚ ist eine gute Angewohnheit, insbesondere beim Schreiben größerer Skripte, aber ‚ ist für einen kleinen Einzeiler wie diesen ein Ooverkill eine.

Antwort

Angenommen, wir haben die Datei test.txt enthält:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Der folgende Code kann verwendet werden:

sed -n "/foo/,/bar/p" test.txt 

Für die folgende Ausgabe:

foo here is the text to keep between the 2 patterns bar 

Antwort

Die grep-Alternative sift unterstützt den mehrzeiligen Abgleich (Haftungsausschluss: Ich bin der Autor).

Angenommen, testfile enthält:

 <book> <title>Lorem Ipsum</title> <description>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua</description> </book> 

sift -m '<description>.*?</description>' (zeigen Sie die Zeilen mit die Beschreibung)

Ergebnis:

 testfile: <description>Lorem ipsum dolor sit amet, consectetur testfile: adipiscing elit, sed do eiusmod tempor incididunt ut testfile: labore et dolore magna aliqua</description> 

sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (extrahieren und Beschreibung neu formatieren)

Ergebnis:

description="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua" 

Kommentare

  • Sehr schönes Tool. Herzliche Glückwünsche! Versuchen Sie, es in Distributionen wie Ubuntu aufzunehmen.

Antwort

Ich habe dieses Problem mit grep und – für mich gelöst Eine Option mit einem anderen Grep.

grep first_line_word -A 1 testfile | grep second_line_word 

Die Option -A 1 druckt 1 Zeile nach der gefundenen Zeile. Natürlich hängt es von Ihrer Datei- und Wortkombination ab. Aber für mich war es die schnellste und zuverlässigste Lösung.

Kommentare

  • alias grepp = ‚ grep –color = auto -B10 -A20 -i ‚ dann cat somefile | grepp blah | grepp foo | Grepp Bar … ja die -A und -B sind sehr praktisch …Sie haben die beste Antwort
  • Dies ist nicht ‚ nicht super deterministisch und ignoriert das gesamte Muster, um nur eine andere einzelne Linie zu erhalten (nur basierend auf ihrer Nähe) in die erste Zeile). ‚ ist es besser, dem Programm mitzuteilen, wie weit es gehen soll, um zu einem Muster zu gelangen, das Sie ‚ sind Absolut sicher ist das Ende des Textes, mit dem Sie ‚ übereinstimmen möchten. Wenn beispielsweise testfile so aktualisiert wird, dass second_line_word in der dritten Zeile steht, fehlt Ihnen jetzt nicht nur die erste Zeile (aufgrund von Ihre zweite grep), aber Sie ‚ verpassen nicht die Zeile, die zwischen den beiden angezeigt wurde.
  • Dies Wäre eine gute MO für Ad-hoc-Befehle, bei denen Sie wirklich nur eine einzige Zeile in der Ausgabe wünschen, die Sie jedoch bereits verstanden haben. Ich glaube nicht, dass ‚

das ist, wonach das OP sucht, und Sie könnten an diesem Punkt wahrscheinlich auch einfach kopieren / einfügen es ist ad hoc.

Antwort

Eine Möglichkeit, dies zu tun, ist Perl. z.B. Hier ist der Inhalt einer Datei mit dem Namen foo:

foo line 1 bar line 2 foo foo foo line 5 foo bar line 6 

Hier ist ein Perl, der dies tun wird Match gegen jede Zeile, die mit foo beginnt, gefolgt von einer Zeile, die mit bar beginnt:

cat foo | perl -e "while(<>){$all .= $_} while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) { print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m; }" 

Das Perl, aufgeschlüsselt:

  • while(<>){$all .= $_} Hiermit wird die gesamte Standardeingabe in die Variable $all
  • Während die Variable all den regulären Ausdruck hat …
  • /^(foo[^\n]*\nbar[^\n]*\n)/m Der reguläre Ausdruck: foo am Anfang der Zeile, gefolgt von einer beliebigen Anzahl von Nicht-Zeilenumbrüchen, gefolgt von einer Zeilenumbruchlinie, unmittelbar gefolgt von „Balken“ und dem Rest der Zeile mit Balken darin. /m am Ende der Regex bedeutet „Übereinstimmung über mehrere Zeilen“
  • print $1 Drucken Sie den Teil der Regex Das war in Klammern (in diesem Fall der gesamte reguläre Ausdruck).
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Löscht die erste Übereinstimmung für den regulären Ausdruck, sodass wir mehrere Fälle des regulären Ausdrucks abgleichen können in der fraglichen Datei

Und die Ausgabe:

foo line 1 bar line 2 foo bar line 6 

Kommentare

  • Schauen Sie einfach vorbei, um zu sagen, dass Ihr Perl auf das Idiomatischere verkürzt werden kann: perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo

Antwort

Wenn wir den Text zwischen den beiden Mustern ohne sich selbst erhalten möchten.

Angenommen, wir haben die Datei test.txt enthält:

blabla blabla foo here is the text to keep between the 2 patterns bar blabla blabla 

Der folgende Code kann verwendet werden:

 sed -n "/foo/{ n b gotoloop :loop N :gotoloop /bar/!{ h b loop } /bar/{ g p } }" test.txt 

Für die folgende Ausgabe:

here is the text to keep between the 2 patterns 

Wie funktioniert es? Schritt für Schritt

  1. /foo/{ wird ausgelöst, wenn die Zeile „foo“
  2. n Ersetzen Sie den Musterraum durch die nächste Zeile, dh das Wort „hier“
  3. b gotoloop verzweigen Sie zur Bezeichnung „gotoloop“
  4. :gotoloop definiert die Bezeichnung „gotoloop“
  5. /bar/!{, wenn das Muster keinen „Balken“ enthält
  6. h Ersetzen Sie den Haltebereich durch ein Muster, sodass „hier“ im Haltebereich
  7. b loop Verzweigung zum Label „loop“
  8. :loop definiert das Label „loop“
  9. N hängt das Muster an den Haltebereich an.
    Nun enthält der Haltebereich:
    „hier“
    „ist das“
  10. :gotoloop Wir sind jetzt bei Schritt 4 und wiederholen die Schleife, bis eine Zeile „bar“ enthält.
  11. /bar/ Die Schleife ist beendet. „bar“ wurde gefunden. “ s der Musterraum
  12. Musterbereich wird durch Haltebereich ersetzt, der alle Zeilen zwischen“ foo „und“ bar „enthält, die während der Hauptschleife gespeichert wurden
  13. p Kopieren des Musterbereichs in die Standardausgabe

Fertig!

Kommentare

  • Gut gemacht, +1. Normalerweise vermeide ich die Verwendung dieser Befehle, indem ich die Zeilenumbrüche in SOH tr ‚ und normale sed-Befehle ausführe und dann die Zeilenumbrüche ersetze.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.