Wie kann ich Dateien durchsuchen, in denen zwei verschiedene Wörter vorkommen?

14

Ich suche nach einer Möglichkeit, Dateien zu durchsuchen, in denen zwei Wortinstanzen in derselben Datei vorhanden sind. Ich habe bis zu diesem Punkt Folgendes verwendet, um meine Suche durchzuführen:

find . -exec grep -l "FIND ME" {} \;

Das Problem, auf das ich stoße, ist, dass das Suchergebnis die Datei nicht liefert, wenn es nicht genau ein Leerzeichen zwischen "FIND" und "ME" gibt. Wie passe ich die frühere Suchzeichenfolge an, bei der beide Wörter "FIND" und "ME" in einer Datei vorhanden sind und nicht "FIND ME"?

Ich benutze AIX.

Chad Harrison
quelle
1
Befinden sich die Wörter irgendwo in der Datei oder immer in derselben Zeile?
Sobrique
Die Absicht war die gleiche Linie.
Chad Harrison
Eine Alternative, wenn sich die Wörter in derselben Zeile befinden, ist die Verwendung eines regulären Ausdrucks mit grep -E/ egrep, der alle Muster beschreibt, an denen Sie interessiert sind (und +anstelle dessen, ;wenn Ihre Suche Unterstützung bietet) +.
MattBianco

Antworten:

21

Mit GNU-Tools:

find . -type f  -exec grep -lZ FIND {} + | xargs -r0 grep -l ME

Sie können standardmäßig Folgendes tun:

find . -type f -exec grep -q FIND {} \; -exec grep -l ME {} \;

Aber das würde zwei greps pro Datei ausführen. Um zu vermeiden, dass so viele greps ausgeführt werden und trotzdem portabel sind, während Zeichen in Dateinamen zulässig sind, können Sie Folgendes tun:

convert_to_xargs() {
  sed "s/[[:blank:]\"\']/\\\\&/g" | awk '
    {
      if (NR > 1) {
        printf "%s", line
        if (!index($0, "//")) printf "\\"
        print ""
      }
      line = $0
    }'
    END { print line }'
}

find .//. -type f |
  convert_to_xargs |
  xargs grep -l FIND |
  convert_to_xargs |
  xargs grep -l ME

Die Idee ist, die Ausgabe von findin ein Format zu konvertieren, das für xargs geeignet ist (das ein Leerzeichen (SPC / TAB / NL, und die anderen Leerzeichen von Ihrem Gebietsschema mit einigen Implementierungen von xargs), getrennte Liste von Wörtern, bei denen einfache, doppelte Anführungszeichen und umgekehrte Schrägstriche möglich sind Leerzeichen und einander entkommen).

Im Allgemeinen können Sie die Ausgabe von find -printnicht nachbearbeiten, da die Dateinamen durch ein Zeilenumbruchzeichen getrennt werden und die in Dateinamen enthaltenen Zeilenumbrüche nicht ausgeblendet werden. Zum Beispiel wenn wir sehen:

./a
./b

Wir haben keine Möglichkeit herauszufinden, ob es sich um eine Datei bin einem Verzeichnis handelt a<NL>.oder ob es sich um die beiden Dateien handelt aund b.

Durch die Verwendung von .//., weil //in einem Dateipfad nicht anders angezeigt werden kann als durch find(weil es kein Verzeichnis mit einem leeren Namen gibt und /in einem Dateinamen nicht zulässig ist), wissen wir, dass, wenn wir eine Zeile sehen, die enthält //, das ist die erste Zeile eines neuen Dateinamens. Wir können diesen awkBefehl also verwenden, um alle Zeilenumbrüche außer denen, die diesen Zeilen vorangehen , zu maskieren.

Wenn wir das obige Beispiel nehmen, findwürde im ersten Fall (eine Datei) ausgegeben:

.//a
./b

Welcher awk entkommt:

.//a\
./b

Das xargssieht es als ein Argument. Und im zweiten Fall (zwei Dateien):

.//a
.//b

Was awkso belassen würde, xargssieht also zwei Argumente.

Stéphane Chazelas
quelle
Warum nicht find ... -print0und grep --nullstattdessen verwenden?
Razzed
@razzed, nicht sicher, was du damit meinst. grep --null(aka -Z) wird im ersten verwendet, ist aber eine GNU-Erweiterung. -print0(eine andere GNU-Erweiterung) würde hier nicht helfen.
Stéphane Chazelas
Vielen Dank. Ich möchte Ihren Shell-Code in ein Skript einbinden, das das Suchverzeichnis als Argument von der Kommandozeile übernimmt. Ich bin mir noch nicht sicher, was das .//.bedeutet, und frage mich, wie ich das ändern kann, um ein Argument von der Kommandozeile zu akzeptieren, sagen wir $1?
StackExchange for All
Vielen Dank. Ist es in Ihrem Befehl erforderlich, -print0mit findund -0mit zu verwenden xargs?
StackExchange for All
@ Tim, nicht sicher, was du meinst. Ich verwende find -print0nirgends in meiner Antwort.
Stéphane Chazelas
8

Wenn die Dateien in einem einzigen Verzeichnis sind und deren Namen enthalten keine Leerzeichen, Tabulator, Newline *, ?noch [Zeichen und beginnen Sie nicht mit -noch .wird dies eine Liste von Dateien erhalten enthält ME, dann schmal , dass bis auf diejenigen , die Enthält auch FIND.

grep -l FIND `grep -l ME *`
user45529
quelle
DAS braucht mehr Upvotes !! Weitaus eleganter als die "akzeptierte" Antwort. Hat für mich gearbeitet.
Roblogic
Habe gerade grep -l CategoryLinearAxis `grep -l labelJsFunction *`nach Dateien gesucht, die beide Attribute enthalten. Was für ein perfekter Weg, es zu tun. +1
WEBjuju
3

Mit awkkönnten Sie auch laufen:

find . -type f  -exec awk 'BEGIN{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; END{if (cx > 0 && cy > 0) print FILENAME}' {} \;

Es verwendet cxund cyfür die Linien zu zählen passenden FINDund jeweils ME. ENDWenn in dem Block beide Zähler> 0 sind, wird der FILENAME.
Dies wäre schneller / effizienter mit gnu awk:

find . -type f  -exec gawk 'BEGINFILE{cx=0; cy=0}; /FIND/{cx++}
/ME/{cy++}; ENDFILE{if (cx > 0 && cy > 0) print FILENAME}' {} +
don_crissti
quelle
2

Oder benutze egrep -eoder grep -Emag das:

find . -type f -exec egrep -le '(ME.*FIND|FIND.*ME)' {} \;

oder

find . -type f -exec grep -lE '(ME.*FIND|FIND.*ME)' {} +

Mit +dem Befehl make find (sofern unterstützt) werden mehrere Datei- (Pfad-) Namen als Argumente zum Befehl hinzugefügt, der bearbeitet wird -exec. Dies spart Prozesse und ist viel schneller, als \;wenn der Befehl für jede gefundene Datei einmal aufgerufen wird.

-type f Stimmt nur mit Dateien überein, um ein Greifen in einem Verzeichnis zu vermeiden.

'(ME.*FIND|FIND.*ME)'ist ein regulärer Ausdruck, der mit jeder Zeile übereinstimmt, die "ME" gefolgt von "FIND" oder "FIND" gefolgt von "ME" enthält. (einfache Anführungszeichen, um zu verhindern, dass die Shell Sonderzeichen interpretiert).

Fügen Sie -idem grepBefehl ein hinzu, damit zwischen Groß- und Kleinschreibung nicht unterschieden wird.

Verwenden Sie, um nur Zeilen abzugleichen, bei denen "FIND" vor "ME" steht 'FIND.*ME'.

Leerzeichen (1 oder mehr, aber nichts anderes) zwischen den Wörtern benötigen: 'FIND +ME'

So lassen Sie Leerzeichen (0 oder mehr, aber nichts anderes) zwischen den Wörtern zu: 'FIND *ME'

Die Kombinationen mit regulären Ausdrücken sind endlos. Wenn Sie nur nacheinander abgleichen möchten, ist egrep sehr leistungsfähig.

MattBianco
quelle
Unterstützen die meisten Greps "-r" nicht? Das würde das "Finden" eliminieren, aber es könnten sich Sockets oder andere nicht-einfache Dateien in dem Baum befinden, der durchsucht wird.
Gestohlener Moment
OP benutzt AIX und hatte findin der Frage.
MattBianco
0

Wenn man die akzeptierte Antwort betrachtet, scheint sie komplexer zu sein, als es sein muss. GNU-Versionen von findund grepund xargsunterstützen NULL-terminierte Strings. Es ist so einfach wie:

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l ME

Sie können Ihren findBefehl so ändern , dass er nach den gewünschten Dateien filtert. Er funktioniert mit Dateinamen, die beliebige Zeichen enthalten. ohne die zusätzliche Komplexität des sedParsens. Wenn Sie die Dateien weiterverarbeiten möchten, fügen Sie eine weitere --nullzur letzten hinzugrep

find . -type f -print0 | xargs -0 grep -l --null FIND | xargs -0 grep -l --null ME | xargs -0 echo

Und als Funktion:

find_strings() {
    find . -type f -print0 | xargs -0 grep -l --null "$1" | xargs -0 grep -l "$2"
}

Verwenden Sie natürlich die akzeptierte Antwort, wenn Sie keine GNU-Versionen dieser Tools ausführen.

aufgewühlt
quelle
1
--null, --print0, -0Sind alle Erweiterungen GNU. Obwohl einige von ihnen heutzutage in anderen Implementierungen zu finden sind, sind sie immer noch nicht portierbar und nicht im POSIX- oder Unix-Standard.
Stéphane Chazelas