Ich suche nach einer Möglichkeit, alle Dateien in einem Verzeichnis aufzulisten, die an einer beliebigen Stelle in der Datei den vollständigen Satz der gesuchten Schlüsselwörter enthalten.
Die Schlüsselwörter müssen also nicht in derselben Zeile stehen.
Ein Weg dies zu tun wäre:
grep -l one $(grep -l two $(grep -l three *))
Drei Schlüsselwörter sind nur ein Beispiel, es können auch zwei oder vier sein und so weiter.
Ein zweiter Weg, den ich mir vorstellen kann, ist:
grep -l one * | xargs grep -l two | xargs grep -l three
Eine dritte Methode, die in einer anderen Frage auftauchte , wäre:
find . -type f \
-exec grep -q one {} \; -a \
-exec grep -q two {} \; -a \
-exec grep -q three {} \; -a -print
Aber das ist definitiv nicht die Richtung, in die ich hier gehe. Ich möchte etwas , das mit weniger Aufwand und möglicherweise nur ein Anruf grep
, awk
, perl
oder ähnliches.
Zum Beispiel gefällt mir, wie awk
Sie Zeilen abgleichen können, die alle Schlüsselwörter enthalten , wie:
awk '/one/ && /two/ && /three/' *
Oder drucken Sie nur die Dateinamen:
awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *
Ich möchte jedoch Dateien suchen, bei denen sich die Schlüsselwörter möglicherweise an einer beliebigen Stelle in der Datei befinden, nicht unbedingt in derselben Zeile.
Bevorzugte Lösungen wären gzip-freundlich, zum Beispiel grep
die zgrep
Variante, die bei komprimierten Dateien funktioniert. Warum ich dies erwähne, ist, dass einige Lösungen angesichts dieser Einschränkung möglicherweise nicht gut funktionieren. Im Beispiel zum awk
Drucken übereinstimmender Dateien können Sie beispielsweise nicht einfach Folgendes tun:
zcat * | awk '/pattern/ {print FILENAME; nextfile}'
Sie müssen den Befehl in etwa wie folgt ändern:
for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done
Aufgrund der Einschränkung müssen Sie also awk
viele Male aufrufen , obwohl Sie dies bei nicht komprimierten Dateien nur einmal tun können. Und natürlich wäre es besser, einfach zawk '/pattern/ {print FILENAME; nextfile}' *
den gleichen Effekt zu erzielen. Daher würde ich Lösungen vorziehen, die dies ermöglichen.
gzip
freundlich sein, nurzcat
die Dateien zuerst.grep
Lösungen einfach angepasst werden, indem man dengrep
Aufrufen ein Präfix voranstelltz
. Es ist nicht erforderlich, dass ich auch die Dateinamen bearbeite.grep
. AFAIK, nurgrep
undcat
haben Standard "Z-Varianten". Ich glaube nicht, dass es einfacher sein wird, als einefor f in *; do zcat -f $f ...
Lösung zu verwenden. Alles andere müsste ein vollständiges Programm sein, das vor dem Öffnen die Dateiformate überprüft oder eine Bibliothek verwendet, um dasselbe zu tun.Antworten:
Wenn Sie gzippte Dateien automatisch verarbeiten möchten, führen Sie dies entweder in einer Schleife mit
zcat
(langsam und ineffizient, da Sieawk
in einer Schleife mehrmals nacheinander suchen, einmal für jeden Dateinamen) aus oder schreiben Sie denselben Algorithmus neuperl
und verwenden Sie dasIO::Uncompress::AnyUncompress
Bibliotheksmodul, das dies kann Dekomprimieren Sie verschiedene Arten von komprimierten Dateien (gzip, zip, bzip2, lzop). oder in Python, das auch Module für den Umgang mit komprimierten Dateien enthält.In dieser
perl
Version könnenIO::Uncompress::AnyUncompress
beliebig viele Muster und Dateinamen (entweder mit Klartext oder mit komprimiertem Text) eingegeben werden.Alle vorherigen Argumente
--
werden als Suchmuster behandelt. Alle nachfolgenden Argumente--
werden als Dateinamen behandelt. Primitive, aber effektive Optionsverwaltung für diesen Job. Ein besseres Optionshandling (z. B. um eine-i
Option für Suchen ohne Berücksichtigung der Groß- / Kleinschreibung zu unterstützen) könnte mit den ModulenGetopt::Std
oder erreicht werdenGetopt::Long
.Führen Sie es so aus:
(Ich werde keine Dateien auflisten
{1..6}.txt.gz
und{1..6}.txt
hier ... sie enthalten nur einige oder alle Wörter "eins" "zwei" "drei" "vier" "fünf" und "sechs" zum Testen. Die in der Ausgabe oben aufgelisteten Dateien Enthalten Sie alle drei Suchmuster. Testen Sie es selbst mit Ihren eigenen Daten.)Ein Hash
%patterns
enthält den vollständigen Satz von Mustern, die Dateien enthalten müssen. Mindestens eines von jedem Mitglied$_pstring
ist eine Zeichenfolge, die die sortierten Schlüssel dieses Hash enthält. Die Zeichenfolge$pattern
enthält einen vorkompilierten regulären Ausdruck, der ebenfalls aus dem%patterns
Hash erstellt wurde.$pattern
wird mit jeder Zeile jeder Eingabedatei verglichen (wobei der/o
Modifikator nur zum Kompilieren verwendet wird,$pattern
da bekannt ist, dass er sich während des Laufs niemals ändert) undmap()
wird verwendet, um einen Hash (% s) zu erstellen, der die Übereinstimmungen für jede Datei enthält.Wenn alle Muster in der aktuellen Datei angezeigt wurden (indem Sie vergleichen, ob
$m_string
(die sortierten Eingaben in%s
) gleich sind$p_string
), drucken Sie den Dateinamen und springen Sie zur nächsten Datei.Dies ist keine besonders schnelle Lösung, aber nicht unangemessen langsam. Die erste Version benötigte 4 Minuten, um nach drei Wörtern in komprimierten Protokolldateien im Wert von 74 MB zu suchen (insgesamt 937 MB unkomprimiert). Diese aktuelle Version dauert 1m13s. Es könnten wahrscheinlich weitere Optimierungen vorgenommen werden.
Eine offensichtliche Optimierung ist dies in Verbindung mit dieser verwenden
xargs
‚s-P
aka--max-procs
mehr Suchen auf Teilmengen der Dateien parallel laufen zu lassen. Dazu müssen Sie die Anzahl der Dateien zählen und durch die Anzahl der Kerne / CPUs / Threads Ihres Systems dividieren (und durch Addieren von 1 aufrunden). Beispiel: In meinem Beispielsatz wurden 269 Dateien durchsucht, und mein System verfügt über 6 Kerne (ein AMD 1090T).Mit dieser Optimierung wurden in nur 23 Sekunden alle 18 übereinstimmenden Dateien gefunden. Dasselbe könnte natürlich mit jeder anderen Lösung geschehen. HINWEIS: Die Reihenfolge der in der Ausgabe aufgelisteten Dateinamen ist unterschiedlich und muss ggf. nachträglich sortiert werden.
Wie von @arekolek festgestellt, können mehrere
zgrep
s mitfind -exec
oderxargs
wesentlich schneller vorgehen. Dieses Skript bietet jedoch den Vorteil, dass es eine beliebige Anzahl von zu suchenden Mustern unterstützt und mit verschiedenen Arten der Komprimierung umgehen kann.Wenn sich das Skript darauf beschränkt, nur die ersten 100 Zeilen jeder Datei zu untersuchen, werden alle Zeilen (in meinem 74-MB-Beispiel mit 269 Dateien) in 0,6 Sekunden durchlaufen. Wenn dies in einigen Fällen nützlich ist, kann es zu einer Befehlszeilenoption gemacht werden (z. B.
-l 100
), aber es besteht das Risiko, dass nicht alle übereinstimmenden Dateien gefunden werden.Übrigens werden laut Handbuch folgende
IO::Uncompress::AnyUncompress
Komprimierungsformate unterstützt:Eine letzte (ich hoffe) Optimierung. Durch die Verwendung des
PerlIO::gzip
Moduls (in debian as gepacktlibperlio-gzip-perl
) anstelle von konnteIO::Uncompress::AnyUncompress
ich die Zeit für die Verarbeitung meiner 74 MB großen Protokolldateien auf ca. 3,1 Sekunden reduzieren. Es gab auch einige kleine Verbesserungen durch die Verwendung eines einfachen Hashes anstattSet::Scalar
(was mit derIO::Uncompress::AnyUncompress
Version auch ein paar Sekunden sparte ).PerlIO::gzip
wurde als schnellster Perl-Gunzip in /programming//a/1539271/137158 empfohlen (gefunden mit einer Google-Suche nachperl fast gzip decompress
)Das Verwenden
xargs -P
mit diesem hat es überhaupt nicht verbessert. Tatsächlich schien es ihn sogar um 0,1 bis 0,7 Sekunden zu verlangsamen. (Ich habe vier Läufe ausprobiert und mein System erledigt andere Dinge im Hintergrund, die das Timing verändern.)Der Preis ist, dass diese Version des Skripts nur komprimierte und nicht komprimierte Dateien verarbeiten kann. Geschwindigkeit vs Flexibilität: 3,1 Sekunden für diese Version vs 23 Sekunden für die
IO::Uncompress::AnyUncompress
Version mit einemxargs -P
Wrapper (oder 1m13s ohnexargs -P
).quelle
for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; done
funktioniert gut, aber in der Tat dauert 3-mal so lange wie meinegrep
Lösung, und ist eigentlich komplizierter.apt-get install libset-scalar-perl
das Skript verwenden. Aber es scheint nicht in einer angemessenen Zeit zu enden.Stellen Sie das Datensatztrennzeichen
.
so ein, dassawk
die gesamte Datei als eine Zeile behandelt wird:Ähnlich mit
perl
:quelle
for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; done
gibt nichts aus.zcat -f "$f"
wenn einige der Dateien nicht komprimiert sind.awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txt
immer noch keine Ergebnisse zurückgegeben, währendgrep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))
die erwarteten Ergebnisse zurückgegeben werden.Bei komprimierten Dateien können Sie eine Schleife über jede Datei erstellen und diese zuerst dekomprimieren. Mit einer leicht geänderten Version der anderen Antworten können Sie dann Folgendes tun:
Das Perl-Skript wird mit
0
Status (Erfolg) beendet, wenn alle drei Zeichenfolgen gefunden wurden. Das}{
ist Perl Abkürzung fürEND{}
. Alle folgenden Aktionen werden ausgeführt, nachdem alle Eingaben verarbeitet wurden. Das Skript wird also mit einem Exit-Status ungleich 0 beendet, wenn nicht alle Zeichenfolgen gefunden wurden. Daher&& printf '%s\n' "$f"
wird der Dateiname nur gedruckt, wenn alle drei gefunden wurden.Oder, um das Laden der Datei in den Speicher zu vermeiden:
Wenn Sie das Ganze wirklich in einem Skript ausführen möchten, können Sie Folgendes tun:
Speichern Sie das obige Skript
foo.pl
irgendwo in Ihrem$PATH
, machen Sie es ausführbar und führen Sie es folgendermaßen aus:quelle
Von allen bisher vorgeschlagenen Lösungen ist meine ursprüngliche Lösung mit grep die schnellste und dauert 25 Sekunden. Der Nachteil ist, dass das Hinzufügen und Entfernen von Keywords mühsam ist. Also habe ich mir ein Skript ausgedacht (synchronisiert
multi
), das das Verhalten simuliert, aber erlaubt, die Syntax zu ändern:Das Schreiben
multi grep one two three -- *
entspricht also meinem ursprünglichen Vorschlag und läuft in der gleichen Zeit ab. Ich kann es auch problemlos für komprimierte Dateien verwenden, indem ichzgrep
stattdessen als erstes Argument verwende.Andere Lösungen
Ich habe auch mit einem Python-Skript experimentiert und dabei zwei Strategien angewendet: zeilenweise Suche nach allen Schlüsselwörtern und stichwortweise Suche in der gesamten Datei. Die zweite Strategie war in meinem Fall schneller. Aber es war langsamer als nur die Verwendung
grep
und endete in 33 Sekunden. Der zeilenweise Keyword-Abgleich ist in 60 Sekunden abgeschlossen.Das Skript von terdon wurde in 54 Sekunden beendet. Eigentlich hat es 39 Sekunden gedauert, weil mein Prozessor Dual-Core ist. Das ist interessant, weil mein Python-Skript 49 Sekunden (und
grep
29 Sekunden) Wandzeit benötigte .Das Skript von cas konnte auch bei einer geringeren Anzahl von Dateien, die mit weniger als
grep
4 Sekunden verarbeitet wurden, nicht in angemessener Zeit beendet werden , sodass ich es beenden musste.Aber sein ursprünglicher
awk
Vorschlag hat, obwohl er langsamer ist als ergrep
ist, potenzielle Vorteile. In einigen Fällen ist zumindest nach meiner Erfahrung zu erwarten, dass alle Schlüsselwörter irgendwo im Kopf der Datei erscheinen sollten, wenn sie überhaupt in der Datei vorhanden sind. Dies verleiht dieser Lösung eine dramatische Leistungssteigerung:Endet in einer Viertelsekunde im Gegensatz zu 25 Sekunden.
Natürlich haben wir möglicherweise nicht den Vorteil, nach Stichwörtern zu suchen, die bekanntermaßen am Anfang der Dateien vorkommen. In einem solchen Fall
NR>100 {exit}
dauert die Lösung ohne 63 Sekunden (50 Sekunden Wandzeit).Nicht komprimierte Dateien
Es gibt keinen signifikanten Unterschied in der Laufzeit zwischen meiner
grep
Lösung und demawk
Vorschlag von cas. Die Ausführung dauert jeweils einen Bruchteil einer Sekunde.Beachten Sie, dass
FNR == 1 { f1=f2=f3=0; }
in diesem Fall die Variableninitialisierung erforderlich ist, um die Zähler für jede nachfolgende verarbeitete Datei zurückzusetzen. Für diese Lösung muss der Befehl an drei Stellen bearbeitet werden, wenn Sie ein Schlüsselwort ändern oder neue hinzufügen möchten. Auf der anderen Seite könnengrep
Sie mit nur| xargs grep -l four
das gewünschte Schlüsselwort anhängen oder bearbeiten.Ein Nachteil einer
grep
Lösung, die eine Befehlsersetzung verwendet, besteht darin, dass sie hängen bleibt, wenn vor dem letzten Schritt an einer beliebigen Stelle in der Kette keine übereinstimmenden Dateien vorhanden sind. Dies hat keine Auswirkung auf diexargs
Variante, da die Pipe abgebrochen wird, sobaldgrep
ein Status ungleich Null zurückgegeben wird. Ich habe mein Skript für die Verwendung aktualisiert,xargs
damit ich nicht selbst damit umgehen muss, wodurch das Skript einfacher wird.quelle
not all(p in text for p in patterns)
not
) ausprobiert und es war in 32 Sekunden fertig, also nicht viel besser, aber es ist auf jeden Fall besser lesbar.PerlIO::gzip
eher unter Verwendung alsIO::Uncompress::AnyUncompress
. Jetzt dauert es nur noch 3,1 Sekunden anstatt 1m13s, um meine 74MB Protokolldateien zu verarbeiten.eval $(lesspipe)
(z. B. in Ihrem.profile
, etc), können Sieless
statt verwendenzcat -f
und Ihrfor
Loop-Wrapperawk
wird in der Lage sein, jede Art von Datei zu verarbeiten, die diesless
kann (gzip, bzip2, xz und mehr) .... less kann erkennen, ob stdout eine Pipe ist und gibt nur einen Stream an stdout aus, wenn dies der Fall ist.Eine weitere Option: Geben Sie Wörter nacheinander ein,
xargs
damit sie fürgrep
die Datei ausgeführt werden.xargs
kann selbst zum Beenden veranlasst werden, sobald eingrep
Rückgabefehler durch Rücksendung255
an ihn gemeldet wird (siehexargs
Dokumentation). Natürlich wird das Laichen von Muscheln und Gabeln, die an dieser Lösung beteiligt sind, diese wahrscheinlich erheblich verlangsamenund es zu schleifen
quelle
_
undfile
? Wird diese Suche in mehreren Dateien als Argument übergeben und gibt Dateien zurück, die alle Schlüsselwörter enthalten?_
, wird es$0
an die gespawnte Shell übergeben - dies würde sich als Befehlsname in der Ausgabe vonps
- Ich würde mich hier an den Master