Überwachen einer Datei, bis eine Zeichenfolge gefunden wird

60

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 benutze ich:

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

Wenn die Zeichenfolge gefunden wird, wird grep wie erwartet beendet, aber ich muss eine Möglichkeit finden, den Befehl tail ebenfalls zu beenden, damit das Skript fortgesetzt werden kann.

Alex Hofsteede
quelle
Ich frage mich, auf welchem ​​Betriebssystem das Original-Poster lief. Auf einem Linux-RHEL5-System stellte ich überrascht fest, dass der Befehl tail einfach stirbt, sobald der Befehl grep die Übereinstimmung gefunden und beendet hat.
ZaSter
4
@ZaSter: Das tailstirbt erst in der nächsten Zeile. Versuchen Sie Folgendes: date > log; tail -f log | grep -m 1 triggerund dann in einer anderen Shell: echo trigger >> logund Sie sehen die Ausgabe triggerin der ersten Shell, aber keine Beendigung des Befehls. Dann versuchen Sie: date >> login der zweiten Shell und der Befehl in der ersten Shell wird beendet. Aber manchmal ist das zu spät; Wir möchten beenden, sobald die Trigger-Zeile angezeigt wird, nicht, wenn die Zeile nach der Trigger-Zeile vollständig ist.
Alfe
Das ist eine hervorragende Erklärung und ein Beispiel, @Alfe.
ZaSter
1
Die elegante, einzeilige, robuste Lösung besteht darin , tail+ grep -qwie die Antwort von 00prometheus zu verwenden
Trevor Boyd Smith,

Antworten:

41

Ein einfacher POSIX-Einzeiler

Hier ist ein einfacher Einzeiler. Es sind keine bashspezifischen 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 auch dann fortgesetzt werden, wenn es tailnoch nicht beendet wurde. Mit dieser einfachen Methode gelangen Sie dorthin:

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

grepwird blockieren, bis es den String gefunden hat, woraufhin es beendet wird. Durch tailAusführen von seiner eigenen Sub-Shell aus können wir es in den Hintergrund stellen, sodass es unabhängig ausgeführt wird. In der Zwischenzeit kann die Haupt-Shell das Skript ausführen, sobald sie beendet ist grep. tailverbleibt in seiner Sub-Shell, bis die nächste Zeile in die Protokolldatei geschrieben wurde, und wird dann beendet (möglicherweise auch, nachdem das Hauptskript beendet wurde). Der Hauptpunkt ist, dass die Pipeline nicht mehr auf taildas Beenden wartet , sodass die Pipeline sofort nach dem Beenden beendet wird grep.

Einige kleine Verbesserungen:

  • Mit der Option -n0 wird taildas Lesen ab der aktuellen letzten Zeile der Protokolldatei gestartet, falls die Zeichenfolge früher in der Protokolldatei vorhanden ist.
  • Möglicherweise möchten Sie tail-F anstelle von -f angeben. Es ist kein POSIX, aber es ermöglicht taildas Arbeiten, auch wenn das Protokoll während des Wartens gedreht wird.
  • Die Option -q anstelle von -m1 grepbeendet das Programm nach dem ersten Auftreten, ohne jedoch die Triggerzeile auszudrucken . Es ist auch POSIX, was -m1 nicht ist.
00prometheus
quelle
3
Dieser Ansatz wird tailfür immer im Hintergrund laufen. Wie würden Sie die tailPID in der Hintergrund-Sub-Shell erfassen und sie in der Haupt-Shell anzeigen? Ich kann die suboptionale Problemumgehung nur finden, indem ich alle mit der Sitzung verbundenen tailProzesse mit beende pkill -s 0 tail.
Rick van der Zwet
1
In den meisten Fällen sollte es kein Problem sein. Der Grund, warum Sie dies in erster Linie tun, ist, dass Sie erwarten, dass mehr Zeilen in die Protokolldatei geschrieben werden. tailwird beendet, sobald versucht wird, in eine defekte Pipe zu schreiben. Die Pipe bricht ab, sobald der grepVorgang abgeschlossen ist. Sobald der grepVorgang abgeschlossen ist, tailwird die Pipe beendet, sobald die Protokolldatei eine weitere Zeile enthält.
00prometheus
Als ich diese Lösung verwendet habe, habe ich nicht den Hintergrund tail -f.
Trevor Boyd Smith
2
@ Trevor Boyd Smith, ja, das funktioniert in den meisten Situationen, aber das OP-Problem war, dass grep erst beendet wird, wenn tail beendet wurde, und tail nicht beendet wird, bis eine andere Zeile in der Protokolldatei erscheint, nachdem grep beendet wurde (wenn tail dies versucht füttere die Pipeline, die durch ein Grep-Ende unterbrochen wurde). Wenn Sie also nicht im Hintergrund arbeiten, wird Ihr Skript erst ausgeführt, wenn eine zusätzliche Zeile in der Protokolldatei und nicht genau in der Zeile, die grep abfängt, angezeigt wird.
00prometheus
Re "trail wird nicht beendet, bis nach [dem erforderlichen Zeichenfolgenmuster] eine weitere Zeile angezeigt wird": Das ist sehr subtil und ich habe das komplett übersehen. Ich habe es nicht bemerkt, weil das gesuchte Muster in der Mitte war und alles schnell ausgedruckt wurde. (Wiederum ist das von Ihnen beschriebene Verhalten sehr subtil)
Trevor Boyd Smith
59

Die akzeptierte Antwort funktioniert bei mir nicht. Außerdem ist sie verwirrend und ändert die Protokolldatei.

Ich benutze so etwas:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"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, können Sie entweder | tee /dev/ttydie Zeile vor dem Testen in der while-Schleife wiederholen.

Rob Whelan
quelle
2
Dies funktioniert, wird jedoch pkillnicht von POSIX angegeben und ist nicht überall verfügbar.
Richard Hansen
2
Sie brauchen keine while-Schleife. Wenn Sie watch mit der Option -g verwenden, können Sie sich den fiesen Befehl pkill ersparen.
l1zard
@ l1zard Kannst du das ausarbeiten? Wie würden Sie das Ende einer Protokolldatei beobachten, bis eine bestimmte Zeile angezeigt wird? (Weniger wichtig, aber ich bin auch neugierig, als watch -g hinzugefügt wurde; ich habe einen neueren Debian-Server mit dieser Option und einen anderen alten RHEL-basierten ohne diese Option).
Rob Whelan
Mir ist nicht ganz klar, warum hier überhaupt Schwanz gebraucht wird. Soweit ich das richtig verstehe, möchte der Benutzer einen bestimmten Befehl ausführen, wenn ein bestimmtes Schlüsselwort in einer Protokolldatei angezeigt wird. Der folgende Befehl using watch erledigt genau diese Aufgabe.
l1zard
Nicht ganz - es wird geprüft, wann eine bestimmte Zeichenfolge zur Protokolldatei hinzugefügt wird . Ich benutze dies, um zu überprüfen, ob Tomcat oder JBoss vollständig gestartet ist. Sie schreiben jedes Mal "Server gestartet" (oder ähnlich), wenn dies passiert.
Rob Whelan
16

Wenn Sie Bash verwenden (zumindest, aber es scheint, dass es nicht durch POSIX definiert ist, so dass es in einigen Shells fehlen kann), können Sie die Syntax verwenden

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

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

petch
quelle
1
Dies funktioniert, aber der Schwanz läuft noch, bis Sie ein SIGTERM(Strg + C, Befehl
beenden
3
@mems, jede zusätzliche Zeile in der Protokolldatei reicht aus. Der tailliest es, versucht es auszugeben und erhält dann ein SIGPIPE, das es beendet. Im Prinzip haben Sie also Recht. Das tailkann auf unbestimmte Zeit ausgeführt werden, wenn nie wieder etwas in die Protokolldatei geschrieben wird. In der Praxis könnte dies für viele Menschen eine sehr saubere Lösung sein.
Alfe
14

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

Schlechter Ansatz: Erzwinge taildas Schreiben einer weiteren Zeile

Sie können taildas Schreiben einer weiteren Ausgabezeile erzwingen , sobald grepeine Übereinstimmung gefunden und beendet wurde. Dies führt dazu tail, dass ein SIGPIPEangezeigt wird und das Programm beendet wird. Eine Möglichkeit, dies zu tun, besteht darin, die Datei zu ändern, die tailnach dem grepBeenden überwacht wird .

Hier ist ein Beispielcode:

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

In diesem Beispiel catwird nicht beendet, bis grepdie Standardausgabe geschlossen wurde. Daher tailist es unwahrscheinlich, dass in die Pipe geschrieben werden kann, bevor grepdie Standardausgabe geschlossen werden konnte. catwird verwendet, um die Standardausgabe von grepunmodified zu verbreiten.

Dieser Ansatz ist relativ einfach, es gibt jedoch mehrere Nachteile:

  • Wenn grepstdout vor stdin geschlossen wird, gibt es immer eine Racebedingung: grepschließt stdout, löst aus cat, löst aus echo, löst aus tail, gibt eine Zeile aus. Wenn diese Zeile an gesendet wurde, grepbevor grepstdin geschlossen werden konnte, tailwird sie erst abgerufen, wenn eine SIGPIPEandere 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 tailfunktioniert 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 bashdas PIPESTATUSArray von). Dies ist in diesem Fall keine große Sache, da grepimmer 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 grepzurü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 # optionalkönnen entfernt werden und das Programm funktioniert weiterhin. tailwird 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 tailPipeline-Phase 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 und ob grepsie beendet wurden.

Bei einer Pipeline wie dieser tail -f ... | grep ...ist es einfach, die erste Pipelinestufe zu ändern, um taildie PID in einer Variablen durch Hintergrundinformationen tailund Lesen zu speichern $!. Es ist auch einfach, die zweite Pipeline-Stufe so zu ändern, dass sie killbeim grepBeenden 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 tailPID irgendwie herausfinden, damit sie tailbei grepRückkehr töten kann , oder die erste Stufe muss irgendwie benachrichtigt werden, wenn grepRückkehr erfolgt.

Die zweite Stufe könnte pgrepzum Abrufen tailder PID verwendet werden, dies wäre jedoch unzuverlässig (möglicherweise stimmen Sie mit dem falschen Prozess überein) und nicht portierbar ( pgrepwird vom POSIX-Standard nicht angegeben).

Die erste Stufe könnte die PID durch echoEingabe der PID über die Pipe an die zweite Stufe senden , aber diese Zeichenfolge wird mit tailder 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 taildie Ausgabe möglicherweise grepfür eine willkürlich lange Zeit nicht verarbeitet wird . Auf GNU-Systemen sollte es keine Probleme geben: GNU grepverwendet read(), wodurch jegliches Puffern vermieden wird, und GNU tail -fruft 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.

Richard Hansen
quelle
Ihre Lösung (wie andere auch, ich werde Ihnen keine Vorwürfe machen) wird Dinge vermissen, die bereits in die Protokolldatei geschrieben wurden, bevor Ihre Überwachung gestartet wurde. Das tail -fgibt nur die letzten zehn Zeilen und dann alle folgenden aus. Um dies zu verbessern, können Sie die Option -n 10000zum Ende hinzufügen , sodass auch die letzten 10000 Zeilen ausgegeben werden.
Alfe
Eine weitere Idee: Ihre Fifo - Lösung kann gerade gerichtet werden, glaube ich, durch die Ausgabe der vorbei tail -fdurch die Fifo und greppen darauf: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe
@Alfe: Ich könnte mich irren, aber ich glaube, wenn ich tail -f login ein FIFO schreibe, werden einige Systeme (z. B. GNU / Linux) blockbasiertes Puffern anstelle von zeilenbasiertem Puffern verwenden, was bedeutet, grepdass die übereinstimmende Zeile möglicherweise nicht angezeigt wird wird im Protokoll angezeigt. Das System bietet möglicherweise ein Hilfsprogramm zum Ändern der Pufferung, z. B. stdbufvon GNU coreutils. Ein solches Dienstprogramm wäre jedoch nicht portabel.
Richard Hansen
1
@Alfe: Tatsächlich sagt POSIX anscheinend nichts über das Puffern aus, außer wenn Sie mit einem Terminal interagieren. Aus Sicht der Standards ist Ihre einfachere Lösung also so gut wie meine komplexe. Ich bin mir jedoch nicht hundertprozentig sicher, wie sich die verschiedenen Implementierungen jeweils tatsächlich verhalten.
Richard Hansen
Eigentlich gehe ich jetzt für das noch einfachere grep -q -m 1 trigger <(tail -f log)anderswo vor und lebe damit, dass das taileine Zeile länger im Hintergrund läuft als es muss.
Alfe
9

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

Vielleicht sollten Sie eine Zeitüberschreitung verwenden, anstatt auf unbestimmte Zeit zu warten.

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

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

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; 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 ist die Protokolldatei noch nicht vorhanden, nachdem Sie Ihren Server gestartet haben. In diesem Fall sollten Sie warten, bis es angezeigt wird, 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="${1:-10}"; 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"
Elifarley
quelle
Also, wo ist das timeoutKommando?
ayanamist
Tatsächlich ist die Verwendung timeoutdie einzige zuverlässige Methode, um nicht auf unbestimmte Zeit auf einen Server zu warten, der nicht gestartet werden kann und bereits beendet wurde.
gluk47
1
Diese Antwort ist die beste. Kopieren Sie einfach die Funktion und rufen Sie sie auf, es ist sehr einfach und wiederverwendbar
Hristo Vrigazov
6

Nachdem ich einige Tests durchgeführt hatte, fand ich einen schnellen Weg, um diese Arbeit zu machen. Es scheint, dass tail -f beendet wird, 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 erreicht, indem ich die leere Zeichenfolge an die Datei angefügt habe, wenn grep die Übereinstimmung findet.

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

Ich bin mir nicht sicher, warum das Öffnen / Schließen der Datei den Endpunkt auslöst, um zu erkennen, dass die Pipe geschlossen ist, sodass ich mich nicht auf dieses Verhalten verlassen würde. aber es scheint vorerst zu funktionieren.

Grund dafür ist, dass es schließt, schauen Sie sich das -F-Flag im Vergleich zum -f-Flag an.

Alex Hofsteede
quelle
1
Dies funktioniert, weil das Anhängen an die Protokolldatei dazu führt tail, dass eine andere Zeile ausgegeben wird, bis dahin jedoch beendet grepwurde (wahrscheinlich - es liegt eine Race-Bedingung vor). Wenn grepbis zum Ende der Zeit eine tailandere Zeile geschrieben wurde, tailwird ein SIGPIPE. Das führt taildazu, dass man sofort abreist.
Richard Hansen
1
Nachteile dieser Vorgehensweise: (1) Es liegt eine Race-Bedingung vor (möglicherweise wird sie nicht immer sofort beendet). (2) Es ist ein Schreibzugriff auf die Protokolldatei erforderlich. (3) Das Ändern der Protokolldatei ist in Ordnung. (4) Möglicherweise ist die Datei beschädigt log file (5) es funktioniert nur für tail(6) Sie können es nicht einfach so einstellen, dass es sich je nach String-Übereinstimmungen unterschiedlich verhält ("server started" vs. "server start failed"), da Sie den Rückkehrcode nicht einfach erhalten können der mittleren Stufe der Pipeline. Es gibt einen alternativen Ansatz, der all diese Probleme vermeidet - siehe meine Antwort.
Richard Hansen
6

Derzeit tail -flaufen alle hier beschriebenen Lösungen Gefahr, eine zuvor protokollierte "Server Started" -Zeile zu übernehmen (was in Ihrem speziellen Fall je nach Anzahl der protokollierten Zeilen und der Protokolldateirotation ein Problem sein kann oder nicht). Kürzung).

Anstatt Dinge zu komplizieren, benutze einfach ein schlaueres tail, wie bmike mit einem Perl-Snippit zeigte. Die einfachste Lösung ist diese retailmit integrierter Regex-Unterstützung mit Start- und Stopp- Bedingungsmustern:

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

Dies folgt der Datei wie eine normale Datei, tail -fbis die erste neue Instanz dieser Zeichenfolge angezeigt wird. Beenden Sie dann die Datei . (Die -uOption wird im normalen "Follow" -Modus nicht für vorhandene Zeilen in den letzten 10 Zeilen der Datei ausgelöst.)


Wenn Sie GNU tail(von coreutils ) verwenden, ist die nächst einfachere Option --pidund ein FIFO (Named Pipe):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

Ein FIFO wird verwendet, da die Prozesse separat gestartet werden müssen, um eine PID zu erhalten und zu übergeben. Ein FIFO leidet immer noch unter dem gleichen Problem , für eine rechtzeitige Schreiben auf Grund rumhängen tailerhielt eine SIGPIPE , verwenden Sie die --pidOption , so dass tailAusfahrten , wenn es merkt , dass grepbeendet hat (üblicherweise verwendet , um die zu überwachen Schreiber Prozess und nicht als der Leser , aber taildoesn‘ ist mir wirklich egal). Option -n 0wird mit verwendet, taildamit alte Zeilen keine Übereinstimmung auslösen.


Sie können auch einen Stateful-Tail verwenden , der den aktuellen Datei-Offset speichert, sodass nachfolgende Aufrufe nur neue Zeilen anzeigen (auch die Dateirotation wird behandelt). In diesem Beispiel wird das alte FWTK retail* verwendet:

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

* Hinweis, gleicher Name, anderes Programm als bei der vorherigen Option.

Vergleichen Sie den Zeitstempel der Datei mit der Statusdatei ( .${LOGFILE}.off), anstatt eine CPU-Überlastungsschleife zu haben , und warten Sie. Verwenden Sie " -T", um den Speicherort der Statusdatei anzugeben, falls erforderlich. Oben wird das aktuelle Verzeichnis vorausgesetzt. Sie können diese Bedingung jederzeit überspringen oder unter Linux die effizientere inotifywaitverwenden:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done
mr.spuratic
quelle
Kann ich retailmit einer Zeitüberschreitung kombinieren, z. B .: "Wenn 120 Sekunden vergangen sind und der Einzelhandel die Zeile noch nicht gelesen hat, geben Sie einen Fehlercode ein und beenden Sie den Einzelhandel"?
15.
@kiltek verwenden GNU timeout(coreutils) zur Einführung retailund prüfen nur für Exit - Code 124 auf timeout ( timeouttöten , was auch immer befehlen Sie es verwenden , nach der Zeit starten Sie setzen)
mr.spuratic
4

Dies ist etwas knifflig, da Sie sich mit der Prozesssteuerung und -signalisierung befassen müssen. Kluger wäre eine Zwei-Skript-Lösung mit PID-Tracking. Besser wäre es, Named Pipes wie diese zu verwenden.

Welches Shell-Skript verwenden Sie?

Für eine schnelle und schmutzige Ein-Skript-Lösung würde ich 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 in der while-Schleife zu drucken, können Sie nach der Zeichenfolgenübereinstimmung filtern und aus der while-Schleife ausbrechen, damit Ihr Skript fortgesetzt wird.

In beiden Fällen sollten Sie nur ein wenig lernen, um die gewünschte Überwachungsflusssteuerung zu implementieren.

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

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 angezeigt wird

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

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

Mykhaylo Adamovych
quelle
2
Diese Abfrage hat zwei Hauptnachteile: 1. Sie verschwendet Rechenzeit, indem sie das Protokoll immer wieder durchläuft. Betrachten Sie eine, /path/to/the.filedie 1,4 GB groß ist; dann ist klar, dass dies ein problem ist. 2. Es wartet länger als nötig, wenn der Protokolleintrag angezeigt wurde, im schlimmsten Fall 10s.
Alfe
2

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

#!/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 noch verbessert werden ...

Vorteile:

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

Dafür brauchst du keinen Schwanz. Ich denke, der Befehl watch ist das, wonach Sie suchen. Der Befehl watch überwacht die Ausgabe einer Datei und kann mit der Option -g beendet werden, wenn sich die Ausgabe ändert.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction
l1zard
quelle
1
Da dies alle zwei Sekunden ausgeführt wird, wird es nicht sofort beendet, sobald die Zeile in der Protokolldatei angezeigt wird. Es funktioniert auch nicht gut, wenn die Protokolldatei sehr groß ist.
Richard Hansen
1

Alex, ich denke, dieser wird dir sehr helfen.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

Dieser Befehl gibt niemals einen Eintrag in die Logdatei, sondern greift stumm ...

Md. Mohsin Ali
quelle
1
Dies funktioniert nicht - Sie müssen es anhängen, logfileda es ansonsten eine willkürlich lange Zeit dauern kann tail, bis eine andere Zeile ausgegeben grepwird und erkannt wird, dass (via SIGPIPE) gestorben ist .
Richard Hansen
1

Hier ist eine viel bessere Lösung, bei der Sie nicht in die Protokolldatei schreiben müssen, was in einigen Fällen sehr gefährlich oder sogar unmöglich ist.

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

Derzeit hat es nur eine Nebenwirkung, der tailProzess bleibt im Hintergrund, bis die nächste Zeile in das Protokoll geschrieben wird.

Sorin
quelle
tail -n +0 -fbeginnt am Anfang der Datei. tail -n 0 -fbeginnt am Ende der Datei.
Stephen Ostermiller
1
Ein weiterer Nebeneffekt, den ich bekomme:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller
Ich glaube, dass "nächste Liste" in dieser Antwort "nächste Zeile" sein sollte.
Stephen Ostermiller
Dies funktioniert, aber ein tailProzess wird weiterhin im Hintergrund ausgeführt.
cbaldan
1

Die anderen Lösungen hier haben mehrere Probleme:

  • Wenn der Protokollierungsprozess bereits unterbrochen ist oder während der Schleife unterbrochen wird, werden sie auf unbestimmte Zeit ausgeführt
  • Bearbeiten eines Protokolls, das nur angezeigt werden soll
  • unnötigerweise eine zusätzliche Datei schreiben
  • keine zusätzliche Logik zulassen

Folgendes habe ich am Beispiel von Tomcat erfunden (entfernen Sie die Hashes, wenn Sie das Protokoll beim Starten anzeigen möchten):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}
user503391
quelle
1

Der tailBefehl kann im Hintergrund ausgeführt werden und seine grepPID wird in der Subshell wiedergegeben. In der grepSubshell kann ein Trap-Handler bei EXIT den tailBefehl beenden .

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")
phio
quelle
1

Lies sie alle. tldr: entkopple die Beendigung von tail von grep.

Die zwei bequemsten Formen sind

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

und wenn du bash hast

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

Aber wenn dich dieser Schwanz im Hintergrund stört, gibt es hier einen schöneren Weg als eine Fifo oder eine andere Antwort. Benötigt Bash.

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

Oder wenn es nicht der Schwanz ist, der Dinge ausgibt,

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID
Ian Kelling
quelle
0

Versuche inotify zu benutzen (inotifywait)

Sie richten inotifywait für jede Dateiänderung ein und überprüfen dann die Datei mit grep. Wenn sie nicht gefunden wird, führen Sie inotifywait erneut aus, wenn sie gefunden wird, beenden Sie die Schleife ... Smth like that

Evengard
quelle
Auf diese Weise müsste die gesamte Datei jedes Mal überprüft werden, wenn etwas darauf geschrieben wird. Funktioniert nicht gut für Protokolldateien.
Grawity
1
Eine andere Möglichkeit besteht darin, zwei Skripts zu erstellen: 1. tail -f logfile.log | grep -m 1 "Server gestartet"> / tmp / found 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / found; kill -KILL - $ MYPID
Evengard
Ich würde mich freuen, wenn Sie Ihre Antwort bearbeiten, um die Erfassung der PID und die anschließende Verwendung von inotifywait anzuzeigen - eine elegante Lösung, die für jemanden, der Grep kennt, aber ein ausgefeilteres Tool benötigt, leicht zu verstehen ist.
bmike
Eine PID von dem, was Sie erfassen möchten? Ich kann versuchen, es zu schaffen, wenn Sie ein bisschen mehr erklären, was Sie wollen
Evengard
0

Sie möchten gehen, sobald die Zeile geschrieben ist, aber Sie möchten auch nach einer Zeitüberschreitung gehen:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi
Adrien
quelle
-2

Wie wäre es damit:

während wahr; machen Sie als ob [ ! -z $ (grep "myRegEx" myLog.log)]; dann brechen; fi; getan

Bei ihrer
quelle