grep, um Instanzen von "Foo" zu finden, bei denen "Bar" nicht innerhalb von 10 Zeilen angezeigt wird

10

Angenommen, ich möchte einen ganzen Baum nach allen CPP-Dateien durchsuchen, in denen "Foo" vorkommt. Ich könnte tun:

find . -name "*.cpp" | xargs grep "Foo"

Angenommen, ich möchte nur die Fälle auflisten, in denen eine andere Zeichenfolge, z. B. "Bar", nicht innerhalb von 3 Zeilen des vorherigen Ergebnisses auftritt.

Also zwei Dateien gegeben:

a.cpp

1 Foo
2 qwerty
3 qwerty

b.cpp

1 Foo
2 Bar
3 qwerty

Ich möchte eine einfache Suche erstellen, bei der "Foo" von a.cpp gefunden wird, "Foo" von b.cpp jedoch nicht.

Gibt es eine Möglichkeit, dies auf relativ einfache Weise zu erreichen?

John Dibling
quelle
Möglicherweise könnte die Lösung in der Option grep -A und / oder grep -B und / oder grep -C sein. Ich versuche es aber ohne Erfolg ....
maurelio79
@ Maurelio79: Meine aktuelle Theorie ist dies. Grep für "Foo" mit -A 10 als Kontext. Pipe das zu grep -v Bar. Leiten Sie das an sed weiter, um den Dateinamen und die Zeilennummer zu erhalten. Leiten Sie das an (etwas?), Um diese Zeile zu drucken.
John Dibling

Antworten:

17

Mit pcregrep:

pcregrep --include='\.cpp$' -rnM 'Foo(?!(?:.*\n){0,2}.*Bar)' .

Der Schlüssel befindet sich in der -MOption, die pcregrepfür mehrere Zeilen eindeutig ist und zum Abgleichen mehrerer Zeilen verwendet wird ( pcregrepzieht bei Bedarf mehr Daten aus der Eingabedatei, wenn der RE dies erfordert).

(?!...)ist der Perl / PCRE-RE-Operator mit negativer Vorausschau. Foo(?!...)stimmt überein Foo, solange ...nicht mit dem Folgenden übereinstimmt.

...Sein (?:.*\n){0,2}.*Bar( .nicht mit einem Zeilenumbruchzeichen übereinstimmend), dh 0 bis 2 Zeilen, gefolgt von einer Zeile mit Bar.

Stéphane Chazelas
quelle
+1: Ausgezeichnet. Vielen Dank; Ich bin sicher, es war nicht einfach, den richtigen regulären Ausdruck herauszufinden. Ich schätze Ihre Bemühungen sehr. Dies scheint genau so zu funktionieren, wie ich es wollte.
John Dibling
2
Nebenfrage, wenn Sie beantworten möchten. Wie haben Sie davon erfahren pcregrep? Ich habe noch nie davon gehört.
John Dibling
@ JohnDibling, ich persönlich habe es kürzlich auf unix.SE herausgefunden . Diese RE ist nicht besonders komplex, insbesondere wenn Sie mit dem RE-Operator mit (?!...)negativer Vorausschau vertraut perlsind.
Stéphane Chazelas
9

Macht nichts, verwenden Sie einfach pcregrepwie vorgeschlagen von @StephaneChazelas.


Das sollte funktionieren:

$ find . -name "*.cpp" | 
    while IFS= read -r file; do 
      grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
    done 

Die Idee ist, den -ASchalter von grep zu verwenden , um die übereinstimmenden Zeilen und die N folgenden Zeilen auszugeben. Sie übergeben das Ergebnis dann durch a grep Barund wenn dies nicht übereinstimmt (exit> 0), geben Sie den Namen der Datei wieder.

Wenn Sie wissen, dass Sie vernünftige Dateinamen haben (keine Leerzeichen, neuen Zeilen oder andere seltsame Zeichen), können Sie Folgendes vereinfachen:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; 
  done 

Beispielsweise:

terdon@oregano foo $ cat a.cpp 
1 Foo
2 qwerty
3 qwerty
terdon@oregano foo $ cat b.cpp 
1 Foo
2 Bar
3 qwerty
terdon@oregano foo $ cat c.cpp 
1 Foo
2 qwerty
3 qwerty
4 qwerty
5. Bar
terdon@oregano foo $ for file in $(find . -name "*.cpp"); do grep -A 3 Foo "$file" | grep -q Bar || echo "$file"; done 
./c.cpp
./a.cpp

Beachten Sie, dass dies c.cpptrotz Enthalten zurückgegeben wird, Barda die Zeile mit Barmehr als 3 Zeilen danach ist Foo. Sie können die Anzahl der zu durchsuchenden Zeilen steuern, indem Sie den Wert ändern, der an Folgendes übergeben wird -A:

$ for file in $(find . -name "*.cpp"); do 
   grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done 
./a.cpp

Hier ist eine kürzere (vorausgesetzt, Sie verwenden bash):

$ shopt -s globstar 
$ for file in **/*cpp; do 
    grep -A 10 Foo "$file" | grep -q Bar || echo "$file"; 
  done

WICHTIG

Wie Stephane Chazelas in den Kommentaren hervorhob, drucken die oben genannten Lösungen auch Dateien, die überhaupt nicht enthalten sind Foo. Dieser vermeidet das:

for file in **/*cpp; do 
  grep -qm 1 Foo "$file" && 
  (grep -A 3 Foo "$file" | grep -q Bar || echo "$file"); 
done
terdon
quelle
+1 ordentlich-o. Etwas komplexer als ich gehofft hatte, aber überhaupt nicht schlecht.
John Dibling
Dies setzt voraus, dass "Foo" nur einmal vorkommt. Dadurch werden auch die Dateien gemeldet, die nicht enthalten sind Foo. Sie haben fehlende Anführungszeichen.
Stéphane Chazelas
@StephaneChazelas danke, Zitate behoben. Sie haben völlig Recht damit, Dateien mit Nein zu melden, Foound ich habe das behoben, aber ich sehe Ihren Standpunkt nicht in Bezug auf mehrere Instanzen von Foo. Es sollte richtig mit ihnen umgehen.
Terdon
@ JohnDibling siehe Updates.
Terdon
1
Es wird keine Datei mit 100 Zeilen "Foo" gefolgt von "Bar" gemeldet.
Stéphane Chazelas
0

Ungetestet bin ich auf meinem Handy:

find . -name "*.cpp" | xargs awk '/foo/{t=$0;c=10}/bar/{c=0;t=""}c{c--}t&&!c{print t;t=""}END&&t{print t}' 

sowas in der Art.

w00t
quelle