Überwachen einer Datei, bis eine Zeichenfolge gefunden wird

84648
Alex Hofsteede

Ich verwende tail -f, um eine Protokolldatei zu überwachen, in die aktiv geschrieben wird. Wenn eine bestimmte Zeichenfolge in die Protokolldatei geschrieben wird, möchte ich die Überwachung beenden und mit dem Rest meines Skripts fortfahren.

Zur Zeit verwende ich:

tail -f logfile.log | grep -m 1 "Server Started" 

Wenn der String gefunden wird, wird Grep wie erwartet beendet, aber ich muss einen Weg finden, den Endbefehl auch zu beenden, damit das Skript fortgesetzt werden kann.

47
Ich frage mich, auf welchem ​​Betriebssystem das ursprüngliche Poster lief. Auf einem Linux-RHEL5-System war ich überrascht zu erfahren, dass der Befehl tail einfach stirbt, sobald der Befehl grep die Übereinstimmung gefunden und beendet hat. ZaSter vor 10 Jahren 0
@ZaSter: Der "Schwanz" stirbt erst in der nächsten Zeile. Versuchen Sie folgendes: `date> log; tail -f log | grep -m 1 trigger` und dann in einer anderen Shell: `echo trigger >> log` und Sie sehen die Ausgabe` trigger` in der ersten Shell, aber keine Beendigung des Befehls. Dann versuche es: `date >> log` in der zweiten Shell und der Befehl in der ersten Shell wird beendet. Aber manchmal ist es zu spät. wir möchten enden, sobald die auslöserlinie erscheint, nicht wenn die zeile nach der auslöserlinie abgeschlossen ist. Alfe vor 9 Jahren 3
Das ist eine ausgezeichnete Erklärung und ein Beispiel, @Alfe. ZaSter vor 9 Jahren 0
Die elegante, einzeilige, robuste Lösung besteht in der Verwendung von "tail" + "grep -q" wie bei der Antwort von 00prometheus] (https://superuser.com/a/900134/28756) Trevor Boyd Smith vor 7 Jahren 1

18 Antworten auf die Frage

51
Rob Whelan

Die akzeptierte Antwort funktioniert nicht für mich, außerdem ist sie verwirrend und ändert die Protokolldatei.

Ich verwende so etwas:

tail -f logfile.log | while read LOGLINE do [[ "$" == *"Server Started"* ]] && pkill -P $$ tail done 

Wenn die Protokollzeile mit dem Muster übereinstimmt, beenden Sie das tailvon diesem Skript gestartete.

Hinweis: Wenn Sie die Ausgabe auch auf dem Bildschirm anzeigen möchten, müssen Sie | tee /dev/ttydie Zeile entweder wiederholen oder in der while-Schleife testen.

Dies funktioniert, aber "pkill" wird von POSIX nicht angegeben und ist nicht überall verfügbar. Richard Hansen vor 11 Jahren 2
Sie brauchen keine while-Schleife. Verwenden Sie watch mit der Option -g, und Sie können sich den unangenehmen Befehl pkill ersparen. l1zard vor 9 Jahren 2
@ l1zard Kannst du das herausarbeiten? Wie würden Sie das Ende einer Protokolldatei beobachten, bis eine bestimmte Zeile angezeigt wird? (Weniger wichtig, aber ich bin auch gespannt, wann watch -g hinzugefügt wurde; ich habe einen neueren Debian-Server mit dieser Option und einen anderen alten RHEL-basierten Server ohne diese Option). Rob Whelan vor 9 Jahren 0
Mir ist nicht ganz klar, warum hier überhaupt Schwanz gebraucht wird. Soweit ich das richtig verstanden habe, möchte der Benutzer einen bestimmten Befehl ausführen, wenn ein bestimmtes Schlüsselwort in einer Protokolldatei angezeigt wird. Der unten angegebene Befehl mit watch erledigt genau diese Aufgabe. l1zard vor 9 Jahren 0
Nicht ganz - es wird geprüft, ob eine angegebene Zeichenfolge der Protokolldatei * hinzugefügt * wird. Ich verwende dies, um zu überprüfen, wann Tomcat oder JBoss vollständig gestartet ist. Sie schreiben jedes Mal "Server gestartet" (oder ähnlich). Rob Whelan vor 9 Jahren 0
`pkill` wird von POSIX nicht spezifiziert, aber auf CentOS, Fedora, Ubuntu, Debian, MacOS und wahrscheinlich vielen anderen vorinstalliert. ndemou vor 7 Jahren 0
Mit grep sparen Sie etwas CPU. Und du kannst immer noch pkill verwenden. laurent vor 6 Jahren 0
24
00prometheus

Ein einfacher POSIX One-Liner

Hier ist ein einfacher Einzeiler. Es sind keine bash-spezifischen oder Nicht-POSIX-Tricks oder sogar eine Named Pipe erforderlich. Alles, was Sie wirklich brauchen, ist die Kündigung zu entkoppeln tailvon grep. Auf diese Weise kann grepdas Skript nach dem Ende fortgesetzt werden, auch wenn es tailnoch nicht beendet ist. Diese einfache Methode bringt Sie also dorthin:

( tail -f -n0 logfile.log & ) | grep -q "Server Started" 

grepwird blockieren, bis der String gefunden wurde, woraufhin er beendet wird. Indem wir tailrun von seiner eigenen Sub-Shell aus ausführen, können wir sie in den Hintergrund stellen, damit sie unabhängig läuft. In der Zwischenzeit kann die Haupt-Shell die Ausführung des Skripts fortsetzen, sobald es beendet wird grep. tailverbleibt in seiner Sub-Shell, bis die nächste Zeile in die Protokolldatei geschrieben wurde, und wird dann beendet (möglicherweise sogar nachdem das Hauptskript beendet wurde). Der Hauptpunkt ist, dass die Pipeline nicht länger auf das Ende wartet tail, und die Pipeline also beendet wird, sobald sie beendet ist grep.

Einige kleinere Verbesserungen:

  • Mit der Option -n0 können tailSie aus der aktuellen letzten Zeile der Protokolldatei lesen, falls die Zeichenfolge früher in der Protokolldatei vorhanden ist.
  • Möglicherweise möchten Sie tail-F anstelle von -f angeben. Es ist nicht POSIX, erlaubt tailaber das Arbeiten, auch wenn das Protokoll während des Wartens gedreht wird.
  • Mit der Option -q anstelle von -m1 wird grepdas Programm nach dem ersten Auftreten beendet, jedoch ohne die Triggerlinie auszudrucken . Es ist auch POSIX, was -m1 nicht ist.
Dieser Ansatz lässt den "Schwanz" für immer im Hintergrund laufen. Wie würden Sie die "Schwanz" -PID in der Hintergrund-Sub-Shell erfassen und sie der Main-Shell aussetzen? Ich kann nur mit der suboptionalen Problemumgehung aufwarten, indem ich alle Session-angehängten "tail" -Prozesse mit "pkill -s 0 tail" töte. Rick van der Zwet vor 7 Jahren 2
In den meisten Anwendungsfällen sollte dies kein Problem sein. Der Grund, aus dem Sie dies tun, besteht in erster Linie darin, dass Sie erwarten, dass mehr Zeilen in die Protokolldatei geschrieben werden. "tail" wird beendet, sobald versucht wird, in eine gebrochene Pipe zu schreiben. Die Pipe wird brechen, sobald "grep" abgeschlossen ist. Sobald "grep" abgeschlossen ist, wird "tail" beendet, nachdem die Protokolldatei eine weitere Zeile enthält. 00prometheus vor 7 Jahren 0
Als ich diese Lösung verwendet habe, habe ich nicht den Hintergrund-`` -f` gemacht. Trevor Boyd Smith vor 7 Jahren 0
@Trevor Boyd Smith, ja, das funktioniert in den meisten Situationen, aber das OP-Problem bestand darin, dass grep nicht beendet wird, wenn tail beendet wird, und tail nicht beendet wird, bis eine andere Zeile in der Protokolldatei _after_ grep beendet wird (wenn tail versucht, dies zu tun füttern die Pipeline, die durch das Ende von grep unterbrochen wurde). Wenn Sie also nicht im Hintergrund stehen, wird Ihr Skript nicht weiter ausgeführt, bis eine zusätzliche Zeile in der Protokolldatei angezeigt wird, und nicht genau in der Zeile, die grep abfängt. 00prometheus vor 7 Jahren 2
Re "trail wird erst beendet, wenn nach [das erforderliche Zeichenkettenmuster] eine weitere Zeile erscheint": Das ist sehr subtil und ich habe das völlig vermisst. Ich habe es nicht gemerkt, weil das Muster, das ich suchte, in der Mitte war und alles schnell ausgedruckt wurde. (Wieder ist das Verhalten, das Sie beschreiben, sehr subtil) Trevor Boyd Smith vor 7 Jahren 0
Ja, dies ist ein eher subtiles Problem, und für die Lösung ist zum vollständigen Verständnis etwas Auspacken erforderlich. Tut mir leid, es ist das Beste, was ich tun kann! :-) 00prometheus vor 7 Jahren 0
Ich habe meine Antwort noch einmal überarbeitet, um zu versuchen, etwas klarer zu erklären, aber dieses Zeug ist ein bisschen geheimnisvoll, daher mache ich es vielleicht nicht sehr gut. 00prometheus vor 7 Jahren 0
13
Richard Hansen

Es gibt verschiedene Möglichkeiten, um tailzum Ausgang zu gelangen :

Schlechter Ansatz: Erzwingen Sie tail, eine weitere Zeile zu schreiben

Sie können erzwingen tail, dass eine weitere Zeile geschrieben wird, nachdem grepeine Übereinstimmung gefunden und beendet wurde. Dies führt dazu tail, dass eine erhalten wird SIGPIPE, die beendet wird. Eine Möglichkeit, dies zu tun, besteht darin, die überwachte Datei durch tailAfter- grepExits zu ändern .

Hier ist ein Beispielcode:

tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; } 

In diesem Beispiel catwird es nicht beendet, bis grepder Standardausgang geschlossen ist. Daher tailist es wahrscheinlich nicht möglich, in die Pipe zu schreiben, bevor grepder Standardauslöser geschlossen wurde. catwird verwendet, um die Standardausgabe von grepunmodified zu verbreiten.

Dieser Ansatz ist relativ einfach, hat jedoch mehrere Nachteile:

  • Wenn grepstdout geschlossen wird, bevor stdin geschlossen wird, gibt es immer eine Race-Bedingung: grepSchließt stdout und löst aus, um catzu beenden echo, zu triggern tailund eine Zeile auszugeben. Wenn diese Zeile an gesendet wird, grepbevor grepstdin geschlossen werden konnte, tailwird sie nicht angezeigt, SIGPIPEbis sie eine andere Zeile schreibt.
  • Es erfordert Schreibzugriff auf die Protokolldatei.
  • Sie müssen mit dem Ändern der Protokolldatei zufrieden sein.
  • Sie können die Protokolldatei beschädigen, wenn Sie gleichzeitig mit einem anderen Prozess schreiben (die Schreibvorgänge sind möglicherweise verschachtelt, sodass eine neue Zeile mitten in einer Protokollnachricht angezeigt wird).
  • Dieser Ansatz ist spezifisch für tail- er funktioniert nicht mit anderen Programmen.
  • Die dritte Pipeline-Stufe macht es schwierig, auf den Rückkehrcode der zweiten Pipeline-Stufe zuzugreifen (es sei denn, Sie verwenden eine POSIX-Erweiterung wie bashdas PIPESTATUSArray ' Array'). Dies ist in diesem Fall keine große Sache, da grepin jedem Fall 0 zurückgegeben wird. Im Allgemeinen wird die mittlere Stufe jedoch möglicherweise durch einen anderen Befehl ersetzt, dessen Rückkehrcode Sie interessiert (z. B. etwas, das 0 zurückgibt, wenn "Server gestartet" erkannt wird, 1 Wenn "Server konnte nicht gestartet werden" erkannt wurde.

Die nächsten Ansätze vermeiden diese Einschränkungen.

Ein besserer Ansatz: Vermeiden Sie Pipelines

Sie können ein FIFO verwenden, um die Pipeline vollständig zu vermeiden, sodass die Ausführung nach grepRückkehr fortgesetzt werden kann . Zum Beispiel:

fifo=/tmp/tmpfifo.$$ mkfifo "$" || exit 1 tail -f logfile.log >$ & tailpid=$! # optional grep -m 1 "Server Started" "$" kill "$" # optional rm "$" 

Die mit dem Kommentar markierten Zeilen # optionalkönnen entfernt werden und das Programm funktioniert weiterhin. tailbleibt so lange, bis sie eine andere Eingabezeile liest oder durch einen anderen Prozess abgebrochen wird.

Die Vorteile dieses Ansatzes sind:

  • Sie müssen die Protokolldatei nicht ändern
  • Der Ansatz funktioniert auch für andere Dienstprogramme tail
  • es leidet nicht unter einer rassebedingung
  • Sie können leicht den Rückgabewert von grep(oder den alternativen Befehl, den Sie verwenden) erhalten.

Der Nachteil dieses Ansatzes ist die Komplexität, vor allem bei der Verwaltung des FIFO: Sie müssen einen temporären Dateinamen sicher generieren und sicherstellen, dass der temporäre FIFO gelöscht wird, auch wenn der Benutzer Ctrl-C in der Mitte von drückt das Skript Dies kann mit einer Falle geschehen.

Alternativer Ansatz: Senden Sie eine Nachricht an Kill tail

Sie können die tailPipeline-Stufe beenden, indem Sie ein Signal wie senden SIGTERM. Die Herausforderung besteht darin, zwei Dinge an derselben Stelle im Code zuverlässig zu kennen: tailDie PID des Benutzers und ob er beendet grepwurde.

Mit einer Pipeline, wie beispielsweise tail -f ... | grep ..., ist es einfach, die erste Pipeline-Stufe so zu ändern, dass die tailPID in einer Variablen durch Hintergrund tailund Lesen gespeichert wird $!. Es ist auch einfach, die zweite Pipeline-Stufe so zu ändern, dass sie killbeim grepBeenden ausgeführt wird. Das Problem ist, dass die beiden Stufen der Pipeline in separaten "Ausführungsumgebungen" (in der Terminologie des POSIX-Standards) ausgeführt werden, sodass die zweite Pipeline-Stufe keine Variablen lesen kann, die von der ersten Pipeline-Stufe festgelegt wurden. Ohne die Verwendung von Shell-Variablen muss entweder die zweite Stufe der tailPID irgendwie ermittelt werden, so dass sie tailbei grepRückkehr abbrechen kann, oder die erste Stufe muss bei der grepRückkehr irgendwie benachrichtigt werden .

Die zweite Stufe könnte verwendet werden pgrep, um taildie PID zu erhalten, aber das wäre unzuverlässig (Sie könnten mit dem falschen Prozess übereinstimmen) und nicht portierbar ( pgrepwird nicht vom POSIX-Standard festgelegt).

Die erste Stufe könnte die PID über die Pipe an die zweite Stufe senden echo, aber diese Zeichenfolge wird mit tailder Ausgabe gemischt . Das Demultiplexen der beiden erfordert möglicherweise ein komplexes Fluchtschema, abhängig von der Ausgabe von 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 "$" || 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 <$ # grep has exited, time to go kill "$" } | { grep -m 1 "Server Started" # notify the first pipeline stage that grep is done echo >$ } # clean up rm "$" 

Dieser Ansatz hat alle Vor- und Nachteile des vorherigen Ansatzes, außer dass er komplizierter ist.

Eine Warnung vor Pufferung

Mit POSIX können die Streams stdin und stdout vollständig gepuffert werden. Dies bedeutet, dass taildie Ausgabe möglicherweise nicht grepbeliebig lange verarbeitet wird. Auf GNU-Systemen sollte es keine Probleme geben: GNU grepverwendet read(), wodurch jegliche Pufferung vermieden wird, und GNU tail -fruft regelmäßig fflush()beim Schreiben in stdout auf. Nicht-GNU-Systeme müssen möglicherweise etwas Besonderes tun, um Puffer zu deaktivieren oder regelmäßig zu leeren.

Ihre Lösung (wie auch andere, ich werde Sie nicht beschuldigen) verpasst Dinge, die bereits in die Protokolldatei geschrieben wurden, bevor Ihre Überwachung beginnt. Das "tail -f" gibt nur die letzten zehn Zeilen aus und dann alle folgenden. Um dies zu verbessern, können Sie die Option "-n 10000" zum Ende hinzufügen, sodass auch die letzten 10000 Zeilen ausgegeben werden. Alfe vor 9 Jahren 0
Eine andere Idee: Ich denke, Ihre FIFO-Lösung lässt sich begradigen, indem Sie die Ausgabe des "tail -f" durch die FIFO-Datei leiten und darauf zugreifen: `mkfifo f; tail -f log> f & tailpid = $! ; grep -m 1 Trigger f; töte $ tailpid; rm f`. Alfe vor 9 Jahren 0
@Alfe: Ich könnte falsch liegen, aber ich glaube, dass das Schreiben von "tail -f log" in ein FIFO einige Systeme (z. B. GNU / Linux) dazu veranlasst, blockbasierte Pufferung anstelle von zeilenbasierter Pufferung zu verwenden, was "grep" bedeutet `Die übereinstimmende Zeile wird möglicherweise nicht angezeigt, wenn sie im Protokoll angezeigt wird. Das System kann ein Dienstprogramm zum Ändern der Pufferung bereitstellen, beispielsweise "stdbuf" von GNU-Coreutils. Ein solches Dienstprogramm wäre jedoch nicht portabel. Richard Hansen vor 9 Jahren 0
@Alfe: Tatsächlich sieht es so aus, als würde POSIX nichts sagen, außer wenn man mit einem Terminal interagiert. Aus Sicht der Standards denke ich, dass Ihre einfachere Lösung genauso gut ist wie meine komplexe. Ich bin jedoch nicht zu 100% sicher, wie sich die verschiedenen Implementierungen tatsächlich jeweils verhalten. Richard Hansen vor 9 Jahren 1
Eigentlich gehe ich jetzt für den noch einfacheren "grep -q -m 1 trigger" (tail -f log) `, der an anderer Stelle vorgeschlagen wird, und lebe damit, dass der" tail "eine Zeile länger im Hintergrund läuft, als er braucht. Alfe vor 9 Jahren 0
@Alfe: Die Ersetzung von `<(foo)` ist spezifisch für Bash (und Zsh, vielleicht auch andere). Es ist nicht POSIX-konform und daher nicht für die Verwendung in einem portablen Skript geeignet. Richard Hansen vor 9 Jahren 0
@Alfe: Mit `<(tail -f log)` läuft auch der Befehl `tail -f log` weiter, nachdem` grep` beendet wurde. In diesem Fall ist dies möglicherweise kein Problem, in anderen Fällen kann dies jedoch ein Problem sein. Richard Hansen vor 9 Jahren 0
Wie ich schrieb: Bei dieser Lösung läuft der "Schwanz" eine Zeile länger als nötig (ohne die Beendigung des "grep" und damit des Hauptbefehls zu stoppen), und dies ist in meinen Fällen zumindest kein Problem. Ich kann mir kaum ein Szenario vorstellen, in dem dies mehr als ein akademisches Problem ist. Alfe vor 9 Jahren 0
** Was die Pufferung und die daraus resultierenden Verzögerungen angeht ** eine kurze Anmerkung für alle, die dies mit einem beliebigen Befehl und nicht nur mit 'tail' versuchen: Versuchen Sie `stdbuf -o0` und` stdbuf -i0` links und rechts der Pipe, falls Sie erleben Verzögerungen. In meinem Fall habe ich versucht, `docker-compose logs 'zu überwachen, und es war die Ausgabe, die gepuffert wurde, nichts mit` grep` zu tun. rsilva4 vor 8 Jahren 0
13
petch

Wenn Sie Bash verwenden (zumindest scheint es jedoch nicht in POSIX definiert zu sein, sodass es in einigen Shells möglicherweise fehlt), können Sie die Syntax verwenden

grep -m 1 "Server Started" <(tail -f logfile.log) 

Es funktioniert ähnlich wie die bereits erwähnten FIFO-Lösungen, ist aber viel einfacher zu schreiben.

Das funktioniert, aber der Schwanz läuft noch, bis Sie ein `SIGTERM` senden (Strg + C, Befehl beenden oder beenden). mems vor 9 Jahren 1
@Mems, jede zusätzliche Zeile in der Protokolldatei reicht aus. Der "Schwanz" liest es, versucht es auszugeben und erhält dann ein SIGPIPE, das es beendet. Im Prinzip hast du also recht. Das `Tail` kann unbegrenzt laufen, wenn jemals wieder etwas in die Protokolldatei geschrieben wird. In der Praxis könnte dies eine sehr nette Lösung für viele Menschen sein. Alfe vor 9 Jahren 3
8
Elifarley

Lassen Sie mich auf @ 00prometheus (die beste Antwort) eingehen.

Vielleicht sollten Sie ein Timeout verwenden, anstatt auf unbestimmte Zeit zu warten.

Die unten stehende Bash-Funktion blockiert, bis der angegebene Suchbegriff erscheint oder eine bestimmte Zeitüberschreitung erreicht wird.

Der Beendigungsstatus ist 0, wenn die Zeichenfolge innerhalb des Timeouts gefunden wird.

wait_str() { local file="$1"; shift local search_term="$1"; shift local wait_time="$"; shift # 5 minutes as default timeout  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'" return 1 } 

Möglicherweise existiert die Protokolldatei erst unmittelbar nach dem Start Ihres Servers. In diesem Fall sollten Sie warten, bis es erscheint, bevor Sie nach der Zeichenfolge suchen:

wait_server() { echo "Waiting for server..." local server_log="$1"; shift local wait_time="$1"; shift  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }  wait_str "$server_log" "Server Started" "$wait_time" }  wait_file() { local file="$1"; shift local wait_seconds="$"; shift # 10 seconds as default timeout  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done  ((++wait_seconds)) } 

So können Sie es verwenden:

wait_server "/var/log/server.log" 5m && \ echo -e "\n-------------------------- Server READY --------------------------\n" 
Wo ist also der Timeout-Befehl? ayanamist vor 7 Jahren 0
Eigentlich ist die Verwendung von "Timeout" die einzige zuverlässige Methode, um nicht unbegrenzt auf einen Server zu warten, der nicht gestartet werden kann und bereits beendet wurde. gluk47 vor 7 Jahren 0
Diese Antwort ist die beste. Kopieren Sie einfach die Funktion und rufen Sie sie an. Sie ist sehr einfach und wiederverwendbar Hristo Vrigazov vor 5 Jahren 1
6
Alex Hofsteede

Nach einigen Tests fand ich einen schnellen einzeiligen Weg, um dies zu erreichen. Es scheint, als würde tail -f beendet, wenn grep beendet wird, aber es gibt einen Haken. Es scheint nur ausgelöst zu werden, wenn die Datei geöffnet und geschlossen wird. Ich habe dies durch Anhängen der leeren Zeichenfolge an die Datei erreicht, wenn grep die Übereinstimmung findet.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \; 

Ich bin nicht sicher, warum das Öffnen / Schließen der Datei das Ende auslöst, um zu erkennen, dass die Pipe geschlossen ist. Ich würde mich also nicht auf dieses Verhalten verlassen. aber es scheint für jetzt zu funktionieren.

Grund ist, dass es geschlossen wird. Schauen Sie sich die -F-Flag im Vergleich zur -f-Flag an.

Dies funktioniert, weil das Anhängen an die Protokolldatei dazu führt, dass "tail" eine weitere Zeile ausgibt, aber dann wurde "grep" beendet (wahrscheinlich - es gibt dort eine Racebedingung). Wenn "grep" beendet ist, wenn "tail" eine andere Zeile schreibt, erhält "tail" ein "SIGPIPE". Das bewirkt, dass "Schwanz" sofort beendet wird. Richard Hansen vor 11 Jahren 1
Nachteile dieses Ansatzes: (1) Es gibt eine Race-Bedingung (es kann nicht immer sofort beendet werden.) (2) Erfordert Schreibzugriff auf die Protokolldatei (3). Sie müssen mit der Änderung der Protokolldatei (4) in Ordnung sein Log-Datei (5) funktioniert nur für "Tail" (6). Sie können es nicht leicht anpassen, um sich je nach den verschiedenen String-Übereinstimmungen unterschiedlich zu verhalten ("Server gestartet" vs. "Server-Start fehlgeschlagen"), da Sie dies nicht ohne weiteres erreichen können der Rückkehrcode der mittleren Stufe der Pipeline. Es gibt einen alternativen Ansatz, der all diese Probleme vermeidet - siehe meine Antwort. Richard Hansen vor 11 Jahren 1
6
mr.spuratic

Derzeit bestehen alle tail -fLösungen, wie angegeben, in der Lage, eine zuvor protokollierte Zeile "Server Started" zu übernehmen (die in Ihrem speziellen Fall möglicherweise ein Problem darstellt, abhängig von der Anzahl der protokollierten Zeilen und der Rotation der Protokolldateien). Verkürzung).

Anstatt Dinge zu kompliziert zu machen, benutze einfach ein klügerestail, wie bmike mit einem Perl-Schnipsel zeigte. Die einfachste Lösung ist retaildie integrierte Regex-Unterstützung mit Start- und Stop- Zustandsmustern:

retail -f -u "Server Started" server.log > /dev/null 

Dies folgt der Datei wie eine normale, tail -fbis die erste neue Instanz dieser Zeichenfolge angezeigt wird, und wird dann beendet. (Die -uOption wird in den letzten 10 Zeilen der Datei nicht in vorhandenen Zeilen ausgelöst, wenn sie sich im normalen "follow" -Modus befindet.)


Wenn Sie GNU tail(von coreutils ) verwenden, ist die nächst einfachste Option die Verwendung --pideines FIFOs (Named Pipe):

mkfifo $ grep -q -m 1 "Server Started" $ & tail -n 0 -f server.log --pid $! >> $ rm $ 

Ein FIFO wird verwendet, weil die Prozesse separat gestartet werden müssen, um eine PID zu erhalten und zu übergeben. Ein FIFO leidet nach wie vor unter dem gleichen Problem von etwa für eine rechtzeitige Schreib hängen verursachen tailerhält eine SIGPIPE, die verwendet --pidOption, so dass tailAusfahrten, wenn er merkt, dass grepkonventionell beendet hat (verwendet, um die zu überwachen Schreiber Prozess und nicht als der Leser, aber taildoesn‘ es ist wirklich egal). Option -n 0wird verwendet, taildamit alte Zeilen keine Übereinstimmung auslösen.


Schließlich können Sie ein stateful-Endstück verwenden, das den aktuellen Datei-Offset speichert, so dass nachfolgende Aufrufe nur neue Zeilen anzeigen (er übernimmt auch die Dateirotation). In diesem Beispiel wird das alte FWTK retail* verwendet:

retail "$" > /dev/null # skip over current content while true; do [ "$" -nt ".$.off" ] &&  retail "$" | grep -q "Server Started" && break sleep 2 done 

* Hinweis, gleicher Name, anderes Programm als die vorherige Option.

Vergleichen Sie anstelle der CPU-Hogging-Schleife den Zeitstempel der Datei mit der Statusdatei ( .$.off) und schlafen Sie. Verwenden Sie " -T", um den Speicherort der Statusdatei anzugeben, falls dies erforderlich ist. Das Obige setzt das aktuelle Verzeichnis voraus. Fühlen Sie sich frei, diese Bedingung zu überspringen, oder unter Linux können Sie inotifywaitstattdessen die effizientere verwenden:

retail "$" > /dev/null while true; do inotifywait -qq "$" &&  retail "$" | grep -q "Server Started" && break done 
Kann ich "Einzelhandel" mit einem Timeout kombinieren, z. B. "Wenn 120 Sekunden vergangen sind und der Handel die Zeile noch immer nicht gelesen hat, geben Sie einen Fehlercode ein und beenden Sie den Verkauf". kiltek vor 6 Jahren 0
@kiltek verwendet GNU `timeout` (coreutils), um` retail` zu starten, und prüft nur, ob der Beendigungscode 124 im Timeout ist (`timeout` beendet den Befehl, den Sie zum Starten verwenden, nachdem die eingestellte Zeit gestartet wurde.) mr.spuratic vor 6 Jahren 0
4
bmike

Dies wird ein wenig schwierig sein, da Sie in die Prozesssteuerung und Signalisierung einsteigen müssen. Mehr kludgey wäre eine Zwei-Skript-Lösung mit PID-Tracking. Besser wäre es, Named Pipes wie folgt zu verwenden.

Welches Shell-Skript verwenden Sie?

Für eine schnelle und schmutzige Skriptlösung - ich würde ein Perl-Skript mit File: Tail erstellen

use File::Tail; $file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7); while (defined($line=$file->read)) { last if $line =~ /Server started/; } 

Anstatt innerhalb der while-Schleife zu drucken, können Sie nach dem übereinstimmenden String suchen und aus der while-Schleife ausbrechen, um das Skript fortzusetzen.

Bei beiden Optionen sollte nur ein wenig gelernt werden, um die von Ihnen gesuchte Flusskontrolle zu implementieren.

mit bash. Mein Perl-Fu ist nicht so stark, aber ich werde es versuchen. Alex Hofsteede vor 13 Jahren 0
Nutze Pfeifen - sie lieben Bash und Bash lieben sie. (und Ihre Backup-Software wird Sie respektieren, wenn sie auf eine Ihrer Pipes trifft) bmike vor 12 Jahren 0
`maxinterval => 300` bedeutet, dass die Datei alle fünf Minuten überprüft wird. Da ich weiß, dass meine Zeile vorübergehend in der Datei erscheint, verwende ich eine viel aggressivere Abfrage: `maxinterval => 0.2, adjustafter => 10000` Stephen Ostermiller vor 9 Jahren 0
2
Mykhaylo Adamovych

Warten Sie, bis die Datei angezeigt wird

while [ ! -f /path/to/the.file ]  do sleep 2; done 

Warten Sie, bis der String in der Datei erscheint

while ! grep "the line you're searching for" /path/to/the.file  do sleep 10; done 

https://superuser.com/a/743693/129669

Diese Abfrage hat zwei Hauptnachteile: 1. Sie verschwendet Rechenzeit, indem sie das Protokoll immer wieder durchläuft. Betrachten Sie ein `/ path / to / the.file`, das 1,4 GB groß ist. dann ist es klar, dass dies ein problem ist. 2. Es wartet länger als nötig, wenn der Protokolleintrag angezeigt wurde, im schlimmsten Fall 10 Sekunden. Alfe vor 9 Jahren 2
2
Giancarlo Sportelli

Ich kann mir keine sauberere Lösung als diese vorstellen:

#!/usr/bin/env bash # file : untail.sh # usage: untail.sh logfile.log "Server Started" (echo $BASHPID; tail -f $1) | while read LINE ; do if [ -z $TPID ]; then TPID=$LINE # the first line is used to store the previous subshell PID else echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break fi done 

ok, vielleicht kann der Name verbessert werden ...

Vorteile:

  • Es werden keine speziellen Dienstprogramme verwendet
  • Es schreibt nicht auf die Festplatte
  • es verlässt anmutig den Schwanz und schließt die Pfeife
  • es ist ziemlich kurz und leicht zu verstehen