Suchen Sie das letzte Vorkommen einer Zeichenfolge in mehreren Dateien

9

Ich muss mehrere Protokolldateien durchsuchen (alle Dateien, die in den letzten 24 Stunden erstellt wurden und alle im selben Verzeichnis gespeichert sind), um das letzte Vorkommen einer Zeichenfolge zu finden. Dies ist der Befehl, den ich geschrieben habe:

find . -mtime 1 | grep fileprefix | xargs grep 'search string' | tail -1

Dies gibt jedoch nur die letzte Zeile für eine Datei zurück. Irgendwelche Vorschläge, wie man dies optimiert, um alle Zeilen zu erhalten?

Lokesh
quelle
Hast du versucht, Schwanz und letzten Grep umzukehren? finden . -mtime 1 | grep fileprefix | xargs tail -1 | grep 'search string'
Mathieu

Antworten:

4

Vorausgesetzt, GNU-Einrichtungen:

find . -mtime -1 -exec bash -c \
'for f; do tac "$f" | grep -m1 fileprefix; done' _ {} +
iruvar
quelle
Können Sie bitte den Zweck von 'bash -c \' erläutern, da ich bereits die Bash-Shell verwende? Auch Zweck von '_ {} +' am Ende.
Lokesh
@Lokesh, Sie können findBefehle für Dateien mit ausführen -exec. Mit bash -cbashfindtac .. | grep -m1 fileprefix
spawnen
Ich habe versucht, die Zeichenfolgenfilterung in der for-Schleife zu erweitern, indem ich den Befehl cut eingefügt habe, dh für f; tac "$ f" | grep -m1 fileprefix | cut -d '' -f4,7-8 aber sobald ich den Befehl cut setze, erhalte ich einen unerwarteten Fehler am Ende der Datei. Können Sie mir bitte vorschlagen, was ich falsch mache?
Lokesh
@lokesh, -d" "mit Schnitt verwenden. Doppelte Anführungszeichen anstelle von einfachen
iruvar
1
Der findBefehl kann nach dem Dateipräfix filtern. das grepsollte dafür nicht gebraucht werden. Es ist auch überraschend, dass die Suchzeichenfolge in dieser Antwort nicht enthalten ist.
Jonathan Leffler
8

Wenn sich alles in einem einzigen Verzeichnis befindet, können Sie Folgendes tun:

for file in *fileprefix*; do
    grep 'search string' "$file" | tail -1
done

Wenn es sich um große Dateien handelt, kann es sinnvoll sein, die Daten zu beschleunigen, indem Sie tacdie Datei in umgekehrter Reihenfolge drucken (letzte Zeile zuerst) und dann grep -m1mit dem ersten Vorkommen übereinstimmen. Auf diese Weise müssen Sie nicht die gesamte Datei lesen:

for file in *fileprefix*; do
    tac file | grep -m1 'search string'
done

Beide gehen davon aus, dass keine übereinstimmenden Verzeichnisse vorhanden sind fileprefix. Wenn dies der Fall ist, wird ein Fehler angezeigt, den Sie einfach ignorieren können. Wenn dies ein Problem ist, suchen Sie nur nach Dateien:

 for file in *fileprefix*; do
    [ -f "$file" ] && tac file | grep -m1 'search string'
 done

Wenn Sie auch den Dateinamen drucken möchten, fügen Sie ihn -Hjedem grepAufruf hinzu. Oder, wenn Sie grepes nicht unterstützen, sagen Sie ihm, dass er auch durchsuchen soll /dev/null. Das ändert nichts an der Ausgabe, aber da grepmehrere Dateien angegeben sind, wird immer der Dateiname für jeden Treffer gedruckt:

for file in *fileprefix*; do
    grep 'search string' "$file" /dev/null | tail -1
done
terdon
quelle
"Auf diese Weise vermeiden Sie, dass Sie die gesamte Datei lesen müssen" - äh? Nein, Sie vermeiden es, die gesamte Datei in grep zu lesen, sondern stellen stattdessen die gesamte Datei über tac. Mir ist nicht klar, dass dies schneller sein würde, obwohl es davon abhängen würde, ob die Übereinstimmung am Anfang oder am Ende der Datei war.
Gilles 'SO - hör auf böse zu sein'
@ Gilles nein, du legst auch nicht die ganze Datei durch tac. Es wird beendet, sobald das erste Spiel gefunden wurde. Ich habe gerade mit einer 832M-Textdatei und einem Muster in der letzten Zeile getestet. grep -m 1 pattern fileWerkzeug ~ 7 Sekunden und tac file | grep -m1 patterndauerte 0.009.
Terdon
4
find . ! -name . -prune -mtime 1 -name 'fileprefix*' \
     -exec sed -se'/searchstring/h;$!d;x' {} +

... funktioniert, wenn Sie eine GNU haben sed, die die -sOption eparate files und ein POSIX unterstützt find.

Sie sollten jedoch wahrscheinlich die ! -type doder -type fQualifikationsmerkmale hinzufügen , da der Versuch, ein Verzeichnis zu lesen, nicht sehr nützlich ist und eine weitere Einschränkung des Bereichs auf reguläre Dateien verhindern könnte, dass ein Lesevorgang an einer Pipe- oder seriellen Gerätedatei hängt.

Die Logik ist unglaublich einfach: Sie sedüberschreibt den halten Speicherplatz mit einer Kopie einer übereinstimmenden Eingabezeile searchstringund dlöscht dann alle Eingabezeilen bis auf die letzte für jede Eingabedatei aus der Ausgabe. Wenn es zur letzten Zeile gelangt, xändert es seine Halte- und Musterbereiche. Wenn also searchstringbeim Lesen der Datei überhaupt etwas gefunden wurde, wird das letzte derartige Ereignis automatisch zur Ausgabe gedruckt, andernfalls wird eine leere Zeile geschrieben. (fügen Sie /./!dan das Ende des sedSkripts , wenn das nicht erwünscht ist) .

Dies führt einen einzelnen sedAufruf pro 65.000 Eingabedateien aus - oder was auch immer Ihr ARG_MAXLimit ist. Dies sollte eine sehr performante Lösung sein und ist ganz einfach zu implementieren.

Wenn Sie auch die Dateinamen mit einer aktuellen GNU möchten, sedkönnen Sie sie mit dem FBefehl in separate Zeilen schreiben oder sie findin einer separaten Liste pro Stapel drucken lassen, indem Sie den -printprimären nachher anhängen +.

mikeserv
quelle
1

Wie wäre es mit:

find . -mtime -1 -name "fileprefix*" -exec sh -c \
'echo "$(grep 'search string' $1 | tail -n 1),$1"' _ {} \;

Das Obige gibt Ihnen eine schöne Ausgabe mit dem letzten Auftreten einer Suchzeichenfolge in jeder Datei, gefolgt vom jeweiligen Dateinamen nach dem Komma (ändern Sie den Teil ", $ 1" unter "Echo", um die Formatierung zu ändern, oder entfernen Sie ihn, falls erforderlich). Die Beispielausgabe, die in Dateien mit dem Präfix "Dateiname" nach der Suchzeichenfolge "10" sucht, lautet wie folgt:

[dmitry@localhost sourceDir]$ find . -mtime -1 -name "file*" -exec  sh -c 'echo "$(grep '10' $1 | tail -n 1),$1"' _ {} \;
Another data 02 10,./file02.log
Some data 01 10,./file01.log
Yet another data 03 10,./file03.log 
Dmitry Aleks
quelle
1
find . -mtime 1 -name 'fileprefix*' -exec grep -Hn 'search string' {} + |
    sort -t: -k1,2 -n | 
    awk -F: '{key=$1 ; $1="" ; $2="" ; gsub(/^  /,"",$0); a[key]=$0} 
             END {for (key in a) { print key ":" a[key] }}'

Dies nutzt GNU grep‚s -Hund -nOptionen immer sowohl die Dateinamen und die Zeilennummer aller Spiele drucken, dann sortiert es durch die Dateinamen und Zeilennummer, und Rohre es in awk, das speichert das letzte Spiel für jeden Dateinamen in einem Array, und schließlich druckt es.

Eine ziemlich Brute-Force-Methode, aber sie funktioniert.

cas
quelle