Wie lese ich eine Zeile von `tail -f` durch eine Pipeline und beende sie dann?

8

Ich möchte Änderungen an einer Datei über verfolgen tail -fund dann die erste Zeile ausdrucken, die einem grepBefehl entspricht, bevor ich sie beende. Was ist der einfachste Weg, dies zu tun? Bisher habe ich mit Dingen experimentiert wie:

tail -f foo.log | grep 'bar' | head -1

Die Pipeline hängt jedoch nur, vermutlich aufgrund von Pufferung. Ich habe es auch versucht

tail -f foo.log | grep --line-buffered 'bar' | head -1

Dadurch wird die Zeile ausgedruckt, aber der Vorgang wird erst beendet, wenn ich ^ C drücke, vermutlich weil zum Beenden eine zweite Eingabezeile erforderlich ist head -1. Was ist der beste Weg, um dieses Problem zu lösen?

Jonderry
quelle

Antworten:

11
tail -f foo.log | grep -m 1 bar

Wenn in die Datei foo.log geschrieben wird, können Sie Folgendes tun:

grep -m 1 bar <( tail -f foo.log )

Es sollte beachtet werden, dass der Schwanz -f im Hintergrund bleibt, bis eine weitere Zeile ausgegeben wird. Wenn dies lange dauert, kann dies problematisch sein. Lösung in einem solchen Fall ist:

grep -m 1 bar <( exec tail -f foo.log ); kill $! 2> /dev/null

kill tötet den übrig gebliebenen Schwanz -f-Prozess und wir verbergen Fehler, da es möglich ist, dass der Schwanz verschwunden ist, wenn kill aufgerufen wird.


quelle
Funktioniert bei mir nicht
Teuer
Was meinst du mit "funktioniert bei mir nicht". Gibt es eine Fehlermeldung? Ist was passiert? Unterstützt Ihr grep die Option -m?
Bah, tut mir leid, ich war zu spät bei der Bearbeitung. Es endet nicht für mich.
Teuer
Dies bedeutet, dass entweder die Grepped-Zeichenfolge nicht vorhanden ist oder die Pipe zu Grep gepuffert ist. Test damit: perl -e '$|++; my $i = "a"; while (1) { print $i, "\n"; $i++; select undef, undef, undef, 0.1}' | grep -m 1 aa- Alle 0,1 Sekunden wird die nächste Zeichenfolge gedruckt, zuerst "a", dann "b", ... "z", "aa", "ab" ... - grep wird nach "aa" beendet, also ist es so sollte ~ 2,7 Sekunden nach dem Start sein. und es funktioniert. Das Problem könnte sein, wenn Sie etwas anderes zwischen tail -f .. und | haben grep - wie ein anderer grep, sed oder so ähnlich.
Oh ... Ich habe auf auth.log getestet und nach 'a' gesucht. Wenn es eine erste Übereinstimmung gibt, scheint es, dass sie nicht beendet wird, bis die Datei ein wenig mehr angehängt wird.
Teuer
2

Das Konstrukt <() funktioniert nur in Bash. Meine Lösung ist:

sleep $timeout &
pid=$!
tail -n +0 --pid=$pid -F "$file" | ( grep -q -m 1 "$regexp" && kill -PIPE $pid )
wait $pid

Neben der Arbeit in einer POSIX-Shell besteht ein weiterer Vorteil darin, dass die Wartezeit mit einem Timeout-Wert begrenzt werden kann. Beachten Sie, dass der Ergebniscode dieses Scriptlets umgekehrt ist: 0 bedeutet, dass das Zeitlimit erreicht wurde.


quelle
Nachdem ich alle Antworten gelesen habe, denke ich, dass diese Antwort ein wenig hervorgehoben werden muss. Obwohl das -m seine Verwendung mit grep hat, ist es das --pid-Flag für tail, das ich zehnmal häufiger verwende. Es ist ein sehr gutes Werkzeug für solche Fälle.
Tommi Kyntola
1

tail -fWarten SIGPIPE, das, wie bereits von depesz ausgeführt, erst tail -fnach einem erfolgreichen grep -m 1Match durch ein erneutes Schreiben in eine geschlossene Pipe ausgelöst werden kann, kann vermieden werden, wenn der tail -fBefehl in den Hintergrund gestellt und seine Hintergrund- grepPID an die Subshell gesendet wird, was wiederum der Fall ist implementiert einen trapOn-Exit, der tail -fexplizit beendet wird.

#(tail -f foo.log & echo ${!} ) |
(tail -f foo.log & echo ${!} && wait ) | 
   (trap 'kill "$tailpid"; trap - EXIT' EXIT; tailpid="$(head -1)"; grep -m 1 bar)
suro
quelle
0

Aufbauend auf der Antwort https://superuser.com/a/275962/37154 wurde mir klar, dass, wenn die tailzu spät starten würde, möglicherweise bereits eine Instanz der angegebenen Zeichenfolge verstrichen ist. Die Lösung besteht darin, die gesamte Datei mit einer Endliste zu versehen.

grep -m 1 bar <( exec tail -n +1 -f foo.log ); kill $!
klackern
quelle
-1

Ich denke, dass das Anwenden von Unpuffer so,

tail -f foo.log | unbuffer -p grep 'bar' | head -1

würde funktionieren. Ich bin jedoch nicht in einem System, in dem ich es testen kann, und ich kann nicht rechtfertigen, warum es funktionieren würde, während grep --line-buffereddies nicht der Fall ist. Ich habe jedoch diese Alternative ausprobiert, die zu funktionieren scheint:

tail -f foo.log | sed -n '/bar/{p;q}'

Wenn die Übereinstimmung mit bargefunden wurde, wird sie pgedruckt und qsofort beendet.

Garyjohn
quelle
Weder arbeiten für mich. (Sie beenden nicht)
Teuer