it-swarm.com.de

Wie kann ich mit grep Muster über mehrere Zeilen hinweg finden?

Ich möchte nach Dateien suchen, die "abc" UND "efg" in dieser Reihenfolge haben. Diese beiden Zeichenfolgen befinden sich in unterschiedlichen Zeilen in dieser Datei. ZB eine Datei mit Inhalt:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

Sollte übereinstimmen.

171
Saobi

Grep reicht für diesen Vorgang nicht aus.

pcregrep , das in den meisten modernen Linux-Systemen verwendet wird, kann als verwendet werden

pcregrep -M  'abc.*(\n|.)*efg' test.txt

Es gibt auch einen neueren pcre2grep . Beide werden vom PCRE-Projekt zur Verfügung gestellt.

pcre2grep ist für Mac OS X über Mac Ports als Teil von Port pcre2 verfügbar:

% Sudo port install pcre2 

und über Homebrew als:

% brew install pcre

oder für pcre2

% brew install pcre2
185
ring bearer

Ich bin mir nicht sicher, ob es mit grep möglich ist, aber sed macht es sehr einfach:

sed -e '/abc/,/efg/!d' [file-with-content]
105
LJ.

Hier ist eine Lösung von dieser Antwort :

  • wenn 'abc' und 'efg' in derselben Zeile stehen können:

    grep -zl 'abc.*efg' <your list of files>
    
  • wenn 'abc' und 'efg' in verschiedenen Zeilen stehen müssen:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>
    

Params:

  • -z Behandelt die Eingabe als eine Menge von Zeilen, die jeweils mit einem Null-Byte anstelle einer Zeilenvorschubzeile abgeschlossen sind. d. h. grep bedroht die Eingabe als eine große Linie.

  • -l Druckname jeder Eingabedatei, aus der die Ausgabe normalerweise gedruckt worden wäre.

  • (?s) aktiviere PCRE_DOTALL, was '.' findet einen beliebigen Buchstaben oder Zeilenumbruch.

65
atti

sed sollte ausreichen, wie das oben erwähnte Poster LJ 

anstelle von! d können Sie einfach p zum Drucken verwenden: 

sed -n '/abc/,/efg/p' file
28
user3897784

Ich habe mich stark auf pcregrep verlassen, aber mit neuerer Version von grep muss pcregrep für viele seiner Funktionen nicht installiert werden. Verwenden Sie einfach grep -P.

Im Beispiel der OP-Frage denke ich, dass die folgenden Optionen gut funktionieren, wobei die zweitbesten dazu passen, wie ich die Frage verstehe:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

Ich habe den Text als/tmp/test1 kopiert und das 'g' gelöscht und als/tmp/test2 gespeichert. Hier ist die Ausgabe, die zeigt, dass die erste die übereinstimmende Zeichenfolge und die zweite nur den Dateinamen zeigt (typisch -o soll Übereinstimmung anzeigen und typisch -l zeigt nur Dateiname). Beachten Sie, dass 'z' für Multilinien erforderlich ist, und '(. |\N)' bedeutet, dass entweder 'etwas anderes als Newline' oder 'Newline' verwendet wird - d. H.

[email protected]:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
[email protected]:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

Um festzustellen, ob Ihre Version neu genug ist, führen Sie man grep aus und sehen Sie, ob etwas Ähnliches oben angezeigt wird:

   -P, --Perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

Das ist von GNU grep 2.10.

12
sage

Dies kann leicht durchgeführt werden, indem zuerst tr verwendet wird, um die Zeilenumbrüche durch ein anderes Zeichen zu ersetzen:

tr '\n' '\a' | grep 'abc.*def' | tr '\a' '\n'

Hier verwende ich das Alarmzeichen \a (ASCII 7) anstelle einer Newline ..__, das in Ihrem Text fast nie vorkommt, und grep kann es mit einem . oder speziell mit \a vergleichen.

9
g.rocket

Sie können dies sehr einfach tun, wenn Sie Perl verwenden können. 

Perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

Sie können dies auch mit einem einzelnen regulären Ausdruck tun. Dazu muss jedoch der gesamte Inhalt der Datei in einer einzigen Zeichenfolge zusammengefasst werden, was bei großen Dateien möglicherweise zu viel Speicherplatz beansprucht. Der Vollständigkeit halber ist hier diese Methode: 

Perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt
6
sundar

Ich weiß nicht, wie ich das mit grep machen würde, aber so etwas würde ich mit awk machen:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

Sie müssen jedoch vorsichtig sein, wie Sie dies tun. Soll der Regex dem Teilstring oder dem gesamten Wort entsprechen? Fügen Sie nach Bedarf\w-Tags hinzu. Auch wenn dies streng mit der von Ihnen in dem Beispiel angegebenen übereinstimmt, funktioniert es nicht ganz, wenn abc ein zweites Mal nach efg erscheint. Wenn Sie damit umgehen möchten, fügen Sie gegebenenfalls ein/in/abc/case usw. hinzu.

5
frankc

awk einliner:

awk '/abc/,/efg/' [file-with-content]
4
Swynndla

Ich habe vor einigen Tagen eine grep-Alternative veröffentlicht, die dies direkt unterstützt, entweder durch mehrzeiliges Matching oder durch Verwendung von Bedingungen. Hoffentlich ist es nützlich für Leute, die hier suchen. So würden die Befehle für das Beispiel aussehen:

Multiline: sift -lm 'abc.*efg' testfile 
Bedingungen: sift -l 'abc' testfile --followed-by 'efg'

Sie können auch angeben, dass 'efg' innerhalb einer bestimmten Anzahl von Zeilen 'abc' folgen muss:
sift -l 'abc' testfile --followed-within 5:'efg'

Weitere Informationen finden Sie unter sift-tool.org .

3
svent

Wenn Sie beide Wörter nebeneinander benötigen, zum Beispiel nicht mehr als 3 Zeilen, können Sie Folgendes tun:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Gleiches Beispiel, jedoch nur * .txt-Dateien gefiltert:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Sie können auch den Befehl grep durch den Befehl egrep ersetzen, wenn Sie auch mit regulären Ausdrücken suchen möchten.

2
Mariano Ruiz

Leider kannst du nicht. Aus den grep-Dokumenten:

grep durchsucht die benannten Eingabedateien (oder Standardeingaben, wenn keine Dateien benannt werden oder wenn ein einzelner Bindestrich (-) als Dateiname angegeben ist) nach lines , das eine Übereinstimmung mit dem angegebenen PATTERN enthält.

2
Kaleb Pederson

Wenn Sie bereit sind, Kontexte zu verwenden, können Sie dies durch Tippen tun

grep -A 500 abc test.txt | grep -B 500 efg

Dies zeigt alles zwischen "abc" und "efg" an, solange sie sich innerhalb von 500 Zeilen befinden.

2
agouge

Während die Sed-Option die einfachste und einfachste ist, ist LJs Einliner leider nicht der tragbarste. Diejenigen, die bei einer Version der C-Shell stecken, müssen ihrem Knall entkommen:

sed -e '/abc/,/efg/\!d' [file]

Dies funktioniert leider nicht in bash et al.

2
bug

sie können grep verwenden, wenn Sie in der Reihenfolge des Musters nicht scharf sind.

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

beispiel

grep -l "vector" *.cpp | xargs grep "map"

grep -l findet alle Dateien, die dem ersten Muster entsprechen, und xargs grep für das zweite Muster. Hoffe das hilft.

1
Balu Mohan

Mit Silber Sucher :

ag 'abc.*(\n|.)*efg'

Ähnlich wie die Antwort des Ringträgers, aber stattdessen mit einer ag. Hier könnten möglicherweise Geschwindigkeitsvorteile des Silber-Suchers glänzen.

1
Shwaydogg
#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done
1
ghostdog74

Ich habe dies verwendet, um eine Fasta-Sequenz aus einer Multi-Fasta-Datei mit der Option -P für grep zu extrahieren:

grep -Pzo ">tig00000034[^>]+" file.fasta > desired_sequence.fasta

-P für Perl-basierte Suchvorgänge -z, um ein Zeilenende in 0 Bytes anstatt in Zeilenumbruchzeichen zu setzen -o, um nur das zu erfassen, was übereinstimmt, da grep die gesamte Zeile zurückgibt (in diesem Fall ist -z in diesem Fall die gesamte Datei). Der Kern des regulären Ausdrucks ist der [^>], der mit "nicht größer als Symbol" übersetzt wird.

1
Jon Boyle

Das Dateipattern *.sh ist wichtig, um zu verhindern, dass Verzeichnisse geprüft werden. Natürlich könnte ein Test das auch verhindern.

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

Das

grep -n -m1 abc $f 

sucht maximal 1 Matching und gibt die Wäschelummer zurück (-n). Wenn eine Übereinstimmung gefunden wurde (test -n ...), finde die letzte Übereinstimmung von efg (finde alle und nimm die letzte mit tail -n 1).

z=$( grep -n efg $f | tail -n 1)

sonst weiter.

Da das Ergebnis so etwas wie 18:foofile.sh String alf="abc"; ist, müssen wir bis zum Zeilenende von ":" wegschneiden.

((${z/:*/}-${a/:*/}))

Sollte ein positives Ergebnis zurückgeben, wenn das letzte Spiel des zweiten Ausdrucks nach dem ersten Spiel des ersten Ausdrucks liegt. 

Dann melden wir den Dateinamen echo $f.

0
user unknown

Wenn Sie eine Schätzung der Entfernung zwischen den beiden gesuchten Zeichenfolgen 'abc' und 'efg' haben, können Sie Folgendes verwenden:

grep -r. -e 'abc' -A num1 -B num2 | grep 'efg'

Auf diese Weise gibt der erste Grep die Zeile mit den Zeilen 'abc' plus # num1 und # num2 danach zurück, und der zweite Grep durchsucht alle diese Zeilen, um die 'efg' zu erhalten. Dann wissen Sie, bei welchen Dateien sie zusammen erscheinen.

0
Benjamin Berend

Als Alternative zu Balu Mohans Antwort ist es möglich, die Reihenfolge der Muster nur mit grep, head und tail zu erzwingen:

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

Dieser ist jedoch nicht sehr hübsch. Lesbarer formatiert:

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

Dadurch werden die Namen aller Dateien gedruckt, bei denen "pattern2" nach "pattern1", oder beide in derselben Zeile stehen :

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

Erläuterung

  • tail -n +i - Druckt alle Zeilen nach der ith inklusive
  • grep -n - füge passende Zeilen mit ihren Zeilennummern ein
  • head -n1 - Nur die erste Zeile drucken
  • cut -d : -f 1 - Druckt die erste Ausschnittspalte mit : als Trennzeichen
  • 2>/dev/null - Silence tail Fehlerausgabe, die auftritt, wenn der $()-Ausdruck leer ist
  • grep -q - stille grep und kehrt sofort zurück, wenn eine Übereinstimmung gefunden wird, da uns nur der Exit-Code interessiert
0
Emil Lundberg

Das sollte auch funktionieren ?!

Perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGV enthält den Namen der aktuellen Datei, wenn aus dem file_list /s-Modifikator über Zeilenumbrüche gelesen wird. 

0
PS12