Es gibt einige Möglichkeiten, um tail
zum Ausgang zu gelangen :
Schlechter Ansatz: Erzwinge tail
das Schreiben einer weiteren Zeile
Sie können tail
das Schreiben einer weiteren Ausgabezeile erzwingen , sobald grep
eine Übereinstimmung gefunden und beendet wurde. Dies führt dazu tail
, dass ein SIGPIPE
angezeigt wird und das Programm beendet wird. Eine Möglichkeit, dies zu tun, besteht darin, die Datei zu ändern, die tail
nach dem grep
Beenden überwacht wird .
Hier ist ein Beispielcode:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
In diesem Beispiel cat
wird nicht beendet, bis grep
die Standardausgabe geschlossen wurde. Daher tail
ist es unwahrscheinlich, dass in die Pipe geschrieben werden kann, bevor grep
die Standardausgabe geschlossen werden konnte. cat
wird verwendet, um die Standardausgabe von grep
unmodified zu verbreiten.
Dieser Ansatz ist relativ einfach, es gibt jedoch mehrere Nachteile:
- Wenn
grep
stdout vor stdin geschlossen wird, gibt es immer eine Racebedingung: grep
schließt stdout, löst aus cat
, löst aus echo
, löst aus tail
, gibt eine Zeile aus. Wenn diese Zeile an gesendet wurde, grep
bevor grep
stdin geschlossen werden konnte, tail
wird sie erst abgerufen, wenn eine SIGPIPE
andere Zeile geschrieben wurde.
- Es erfordert Schreibzugriff auf die Protokolldatei.
- Sie müssen mit dem Ändern der Protokolldatei einverstanden sein.
- Sie können die Protokolldatei beschädigen, wenn Sie gleichzeitig mit einem anderen Prozess schreiben (die Schreibvorgänge werden möglicherweise verschachtelt, sodass eine neue Zeile in der Mitte einer Protokollmeldung angezeigt wird).
- Dieser Ansatz ist spezifisch für: Er
tail
funktioniert nicht mit anderen Programmen.
- Die dritte Pipeline-Stufe erschwert den Zugriff auf den Rückkehrcode der zweiten Pipeline-Stufe (es sei denn, Sie verwenden eine POSIX-Erweiterung wie
bash
das PIPESTATUS
Array von). Dies ist in diesem Fall keine große Sache, da grep
immer 0 zurückgegeben wird. Im Allgemeinen wird die mittlere Stufe jedoch durch einen anderen Befehl ersetzt, dessen Rückkehrcode Sie interessieren (z. B. etwas, das 0 zurückgibt, wenn "Server gestartet" erkannt wird, 1 wenn "Server konnte nicht gestartet werden" erkannt wird).
Die nächsten Ansätze umgehen diese Einschränkungen.
Ein besserer Ansatz: Vermeiden Sie Pipelines
Sie können ein FIFO verwenden, um die Pipeline vollständig zu umgehen und die Ausführung fortzusetzen, sobald sie grep
zurückkehrt. Zum Beispiel:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
Die mit dem Kommentar gekennzeichneten Zeilen # optional
können entfernt werden und das Programm funktioniert weiterhin. tail
wird nur so lange verweilen, bis eine andere Eingabezeile gelesen oder durch einen anderen Prozess beendet wird.
Die Vorteile dieses Ansatzes sind:
- Sie müssen die Protokolldatei nicht ändern
- Der Ansatz funktioniert auch für andere Versorgungsunternehmen
tail
- es leidet nicht unter einer Rennbedingung
- Sie können leicht den Rückgabewert von
grep
(oder den von Ihnen verwendeten alternativen Befehl) abrufen.
Der Nachteil dieses Ansatzes ist die Komplexität, insbesondere die Verwaltung des FIFO: Sie müssen sicher einen temporären Dateinamen generieren und sicherstellen, dass das temporäre FIFO gelöscht wird, auch wenn der Benutzer in der Mitte von Strg-C drückt das Drehbuch. Dies kann mit einer Falle geschehen.
Alternativer Ansatz: Senden Sie eine Nachricht an Kill tail
Sie können die tail
Pipeline-Phase beenden, indem Sie ein Signal wie senden SIGTERM
. Die Herausforderung besteht darin, zwei Dinge an derselben Stelle im Code zuverlässig zu kennen: tail
die PID und ob grep
sie beendet wurden.
Bei einer Pipeline wie dieser tail -f ... | grep ...
ist es einfach, die erste Pipelinestufe zu ändern, um tail
die PID in einer Variablen durch Hintergrundinformationen tail
und Lesen zu speichern $!
. Es ist auch einfach, die zweite Pipeline-Stufe so zu ändern, dass sie kill
beim grep
Beenden ausgeführt wird. Das Problem ist, dass die beiden Phasen der Pipeline in separaten "Ausführungsumgebungen" (in der Terminologie des POSIX-Standards) ausgeführt werden, sodass die zweite Pipeline-Phase keine Variablen lesen kann, die von der ersten Pipeline-Phase festgelegt wurden. Ohne die Verwendung von Shell-Variablen muss entweder die zweite Stufe die tail
PID irgendwie herausfinden, damit sie tail
bei grep
Rückkehr töten kann , oder die erste Stufe muss irgendwie benachrichtigt werden, wenn grep
Rückkehr erfolgt.
Die zweite Stufe könnte pgrep
zum Abrufen tail
der PID verwendet werden, dies wäre jedoch unzuverlässig (möglicherweise stimmen Sie mit dem falschen Prozess überein) und nicht portierbar ( pgrep
wird vom POSIX-Standard nicht angegeben).
Die erste Stufe könnte die PID durch echo
Eingabe der PID über die Pipe an die zweite Stufe senden , aber diese Zeichenfolge wird mit tail
der Ausgabe von gemischt . Das Demultiplexen der beiden kann abhängig von der Ausgabe von ein komplexes Escape-Schema erfordern tail
.
Sie können ein FIFO verwenden, damit die zweite Pipeline-Stufe die erste Pipeline-Stufe benachrichtigt, wenn sie beendet wird grep
. Dann kann die erste Stufe töten tail
. Hier ist ein Beispielcode:
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
Dieser Ansatz hat alle Vor- und Nachteile des vorherigen Ansatzes, mit der Ausnahme, dass er komplizierter ist.
Eine Warnung zum Puffern
Mit POSIX können die Streams stdin und stdout vollständig gepuffert werden, was bedeutet, dass tail
die Ausgabe möglicherweise grep
für eine willkürlich lange Zeit nicht verarbeitet wird . Auf GNU-Systemen sollte es keine Probleme geben: GNU grep
verwendet read()
, wodurch jegliches Puffern vermieden wird, und GNU tail -f
ruft regelmäßig auf, fflush()
wenn auf stdout geschrieben wird. Nicht-GNU-Systeme müssen möglicherweise etwas Besonderes tun, um Puffer zu deaktivieren oder regelmäßig zu leeren.