Wie kann ich Muster über mehrere Zeilen hinweg "greifen"?


24

Es scheint, ich missbrauche grep/ egrep.

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 las schließlich, dass diese Werkzeuge pro Zeile funktionieren (auch meine regulären Ausdrücke waren so trivial, dass es nicht das Problem sein konnte).

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


Mögliches Duplikat der
Mehrzeiligen Musterübereinstimmung

1
@CiroSantilli - Ich glaube nicht, dass dieses Q und das, mit dem Sie verlinkt sind, Duplikate sind. Das andere Q fragt, wie Sie einen mehrzeiligen Mustervergleich durchführen (dh welches Tool sollte / kann ich verwenden, um dies zu tun), während das andere fragt, wie dies zu tun ist grep. Sie sind eng miteinander verwandt, aber keine Trottel, IMO.
SLM

@sim diese Fälle sind schwer zu entscheiden: Ich kann Ihren Standpunkt sehen. Ich denke, dieser spezielle Fall ist besser als Duplikat, da der Benutzer "grep"das Verb "to grep" vorschlägt und Top-Antworten, einschließlich der akzeptierten, grep nicht verwenden.
Ciro Santilli新疆改造中心法轮功六四事件

Antworten:


24

Hier ist eine sed, die Ihnen grepüber mehrere Zeilen hinweg ein ähnliches Verhalten verleiht :

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

Wie es funktioniert

  • -n Unterdrückt das Standardverhalten beim Drucken jeder Zeile
  • /foo/{}instruiert sie übereinstimmen foomit den entsprechenden Linien und das zu tun , was im Inneren der squigglies kommt. Ersetzen Sie foodurch den Anfangsteil des Musters.
  • :start ist ein Verzweigungslabel, das uns hilft, die Schleife zu durchlaufen, bis wir das Ende unseres regulären Ausdrucks gefunden haben.
  • /bar/!{}führt aus, was in den Schnörkeln zu den Linien ist, die nicht zusammenpassen bar. Ersetzen Sie bardurch den Endteil des Musters.
  • Nfügt die nächste Zeile an den aktiven Puffer an ( sednennt dies den Musterraum)
  • b startverzweigt bedingungslos zu dem startzuvor erstellten Etikett, um die nächste Zeile anzuhängen, solange der Musterbereich nichts enthält bar.
  • /your_regex/pdruckt den Musterbereich, wenn er übereinstimmt your_regex. Sie sollten your_regexdurch den gesamten Ausdruck ersetzen, den Sie über mehrere Zeilen hinweg abgleichen möchten.

1
+1 Dem Toolikt hinzufügen! Vielen Dank.
wmorrison365

Hinweis: Unter MacOS erhalten Siesed: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
Erste sed: unterminated {Fehler
Nomaed

@Nomaed Hier in der Dunkelheit gedreht, aber enthält Ihre Regex zufällig "{" Zeichen? Wenn ja, müssen Sie sie mit Backslash-Escape versehen.
Joseph R.

1
@Nomaed Es scheint, dass es mit den Unterschieden zwischen den sedImplementierungen zu tun hat . Ich habe versucht, den Empfehlungen in dieser Antwort zu folgen, um das obige Skript standardkonform zu machen, aber es hat mir gesagt, dass "start" eine undefinierte Bezeichnung ist. Ich bin mir also nicht sicher, ob dies auf standardkonforme Weise möglich ist. Wenn Sie es schaffen, können Sie meine Antwort gerne bearbeiten.
Joseph R.

19

Ich benutze im Allgemeinen ein Tool namens , pcregrepdie in den meisten Linux - Geschmack installiert werden kann , mit yumoder apt.

Zum Beispiel.

Angenommen, Sie haben eine Datei mit dem Namen testfilecontent

abc blah
blah blah
def blah
blah blah

Sie können den folgenden Befehl ausführen:

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

Pattern Matching über mehrere Zeilen durchführen.

Darüber hinaus können Sie dasselbe auch mit sed.

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

5

Hier ist ein einfacherer Ansatz mit Perl:

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

oder (da JosephR den sedWeg eingeschlagen hat , werde ich seinen Vorschlag schamlos stehlen )

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

Erläuterung

$f=join("",<>);: liest die gesamte Datei und speichert deren Inhalt (Zeilenumbrüche und alles) in der Variablen $f. Wir versuchen dann, eine Übereinstimmung zu foo\nbar.*\nfinden und drucken sie, falls sie übereinstimmt (die spezielle Variable $&enthält die zuletzt gefundene Übereinstimmung). Das///m wird benötigt, damit der reguläre Ausdruck über Zeilenumbrüche hinweg übereinstimmt.

Die -0Einstellung der Eingangsdatensatztrennzeichen . Wenn Sie diese 00Option aktivieren, 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:

Sie nicht tun dies für große Dateien, wird die gesamte Datei in den Speicher geladen werden und das kann ein Problem sein.


2

Eine Möglichkeit, dies zu tun, ist Perl. zB 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 etwas Perl, das mit jeder Zeile, die mit foo beginnt, gefolgt von jeder Zeile, die mit bar beginnt, übereinstimmt:

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 .= $_} Dadurch wird die gesamte Standardeingabe in die Variable geladen $all
  • while($all =~Während die Variable allden regulären Ausdruck hat ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/mDer reguläre Ausdruck: foo am Zeilenanfang, gefolgt von einer beliebigen Anzahl von Nicht-Zeilenumbrüchen, gefolgt von einem Zeilenumbruch, unmittelbar gefolgt von "bar" und dem Rest der Zeile mit einem Balken. /mam Ende des regulären Ausdrucks bedeutet "Übereinstimmung über mehrere Zeilen"
  • print $1 Gibt den in Klammern gesetzten Teil des regulären Ausdrucks aus (in diesem Fall den gesamten regulären Ausdruck).
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Löschen Sie die erste Übereinstimmung für den regulären Ausdruck, damit mehrere Fälle des regulären Ausdrucks in der betreffenden Datei übereinstimmen können

Und die Ausgabe:

foo line 1
bar line 2
foo
bar line 6

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

2

Der grep alternative sift unterstützt Multiline Matching (Disclaimer: Ich bin der Autor).

Angenommen, testfileenthält:

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


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

Ergebnis:

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


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

Ergebnis:

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

1
Sehr schönes Werkzeug. Herzliche Glückwünsche! Versuchen Sie es in Distributionen wie Ubuntu aufzunehmen.
Lourenco

2

Einfach ein normaler grep, der Perl-regexpparameter unterstützt, erledigt Pdiese Aufgabe.

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

(?s) DOTALL-Modifikator, der in Ihrem Regex einen Punkt erzeugt, der nicht nur den Zeichen, sondern auch den Zeilenumbrüchen entspricht.


Wenn ich diese Lösung versuche, endet die Ausgabe nicht bei 'def', sondern geht zum Ende der Datei 'blah'
buckley

Vielleicht unterstützt Ihr Grep keine -POption
Avinash Raj

1

Ich habe dieses Problem mit grep und der Option -A mit einem anderen grep gelöst.

grep first_line_word -A 1 testfile | grep second_line_word

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


alias grepp = 'grep --color = auto -B10 -A20 -i' dann cat somefile | grepp bla | grepp foo | grepp bar ... ja die -A und -B sind sehr praktisch ... Sie haben die beste Antwort
Scott Stensland

1

Angenommen, wir haben die Datei test.txt mit:

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

1

Wenn wir den Text zwischen den 2 Mustern erhalten möchten, schließen Sie sich aus.

Angenommen, wir haben die Datei test.txt mit:

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 es funktioniert, machen wir es Schritt für Schritt

  1. /foo/{ wird ausgelöst wenn line "foo" enthält
  2. n Ersetzen Sie den Musterraum durch die nächste Zeile, dh das Wort "hier"
  3. b gotoloop Abzweig zum Label "gotoloop"
  4. :gotoloop definiert das Label "gotoloop"
  5. /bar/!{ wenn das Muster keinen "Balken" enthält
  6. h Ersetzen Sie den Laderaum durch ein Muster, damit "hier" im Laderaum gespeichert wird
  7. b loop Abzweig zum Label "loop"
  8. :loop definiert das Label "loop"
  9. N Hängt das Muster an den Laderaum an.
    Nun enthält hold space:
    "here"
    "ist das"
  10. :gotoloop Wir sind jetzt bei Schritt 4 und schleifen, bis eine Zeile "bar" enthält.
  11. /bar/ Endlosschleife ist beendet, "Balken" wurde gefunden, es ist der Musterraum
  12. g Der Pattern Space wird durch einen Hold Space ersetzt, der alle Zeilen zwischen "foo" und "bar" enthält, die während der Hauptschleife gespeichert wurden
  13. p Kopieren Sie den Musterbereich in die Standardausgabe

Getan !


Gut gemacht, +1. Normalerweise vermeide ich die Verwendung dieser Befehle, indem ich die Zeilenumbrüche in SOH eingebe und normale sed-Befehle ausführe und dann die Zeilenumbrüche ersetze.
A.Danischewski
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.