Monitorowanie pliku do momentu znalezienia ciągu


60

Używam tail -f do monitorowania pliku dziennika, który jest aktywnie zapisywany. Po zapisaniu określonego ciągu do pliku dziennika chcę zakończyć monitorowanie i kontynuować resztę skryptu.

Obecnie używam:

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

Gdy łańcuch zostanie znaleziony, grep kończy działanie zgodnie z oczekiwaniami, ale muszę znaleźć sposób, aby polecenie tail również się zakończyło, aby skrypt mógł kontynuować.


Zastanawiam się, na jakim systemie operacyjnym działał oryginalny plakat. W systemie Linux RHEL5 zdziwiłem się, widząc, że polecenie tail po prostu umiera, gdy polecenie grep znajdzie dopasowanie i zakończy działanie.
ZaSter

4
@ZaSter: tailUmiera tylko w następnym wierszu. Spróbuj tego: date > log; tail -f log | grep -m 1 triggera następnie w innej powłoce: echo trigger >> loga zobaczysz wynik triggerw pierwszej powłoce, ale bez zakończenia polecenia. Następnie spróbuj: date >> logw drugiej powłoce, a polecenie w pierwszej powłoce zostanie zakończone. Ale czasem jest już za późno; chcemy zakończyć, gdy tylko pojawi się linia wyzwalająca, a nie po zakończeniu linii po linii wyzwalającej.
Alfe

To doskonałe wyjaśnienie i przykład @Alfe.
ZaSter,

1
eleganckim, jednoliniowym, solidnym rozwiązaniem jest użycie tail+ grep -qodpowiedzi 00prometheus
Trevor Boyd Smith

Odpowiedzi:


41

Prosty jednowarstwowy POSIX

Oto prosty jednowarstwowy. Nie potrzebuje sztuczek specyficznych dla basha, nie-POSIX-owych, ani nawet nazwanego potoku. Jedyne, czego naprawdę potrzebujesz, to odłączyć termin tailod grep. W ten sposób po grepzakończeniu skrypt może kontynuować, nawet jeśli tailjeszcze się nie zakończył. Więc ta prosta metoda doprowadzi cię tam:

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

grepbędzie blokować, dopóki nie znajdzie łańcucha, po czym zakończy działanie. Poprzez tailbieg z jego własnym sub-shell, możemy umieścić go w tle tak, że działa niezależnie. Tymczasem główna powłoka może kontynuować wykonywanie skryptu zaraz po grepwyjściu. tailpozostanie w swojej podpowłoce, dopóki następny wiersz nie zostanie zapisany w pliku dziennika, a następnie zakończy działanie (być może nawet po zakończeniu skryptu głównego). Najważniejsze jest to, że rurociąg nie czeka już na tailzakończenie, więc rurociąg kończy się natychmiast po jego zakończeniu grep.

Kilka drobnych poprawek:

  • Opcja -n0 tailpowoduje, że zaczyna on czytać od bieżącego ostatniego wiersza pliku dziennika, w przypadku gdy łańcuch istnieje wcześniej w pliku dziennika.
  • Możesz podać tail-F zamiast -f. To nie jest POSIX, ale pozwala tailna pracę, nawet jeśli dziennik zostanie obrócony podczas oczekiwania.
  • Opcja -q zamiast -m1 powoduje grepwyjście po pierwszym wystąpieniu, ale bez drukowania linii wyzwalającej. Jest to również POSIX, którego nie ma -m1.

3
Ta aplikacja pozostawi tailbieg w tle na zawsze. Jak przechwycić tailPID w tle podpowłoki w tle i odsłonić ją jako główną powłokę? Mogę tylko wymyślić nieobowiązkowe obejście, zabijając wszystkie tailprocesy dołączone do sesji , używając pkill -s 0 tail.
Rick van der Zwet

1
W większości przypadków nie powinno to stanowić problemu. Powodem, dla którego to robisz, jest to, że oczekujesz, że do pliku dziennika zostanie zapisanych więcej wierszy. tailzakończy się, gdy tylko spróbuje napisać na zepsutej rurze. Rura pęknie, gdy tylko się grepzakończy, więc po grepzakończeniu tailzakończy się, gdy plik dziennika znajdzie w nim jeszcze jedną linię.
00prometheus,

kiedy korzystałem z tego rozwiązania, nie tworzyłem tła tail -f.
Trevor Boyd Smith

2
@Trevor Boyd Smith, tak, że działa w większości sytuacji, ale problemem było to, że PO nie będzie kompletna grep aż ogon jest zamykany, a ogon nie wyjdzie dopóki nie pojawi się kolejny wiersz w pliku dziennika po grep ma zakończyć (gdy ogon próbuje podaj rurociąg, który został przerwany przez zakończenie grep). Więc jeśli nie masz tła w tle, skrypt nie będzie kontynuował wykonywania, dopóki w pliku dziennika nie pojawi się dodatkowa linia, a nie dokładnie w linii, którą łapie grep.
00prometeusz

Re „trail nie opuści się, dopóki po [wymaganym wzorze ciągu nie pojawi się kolejna linia]”: To bardzo subtelnie i całkowicie tego przegapiłem. Nie zauważyłem, ponieważ wzór, którego szukałem, znajdował się na środku i wszystko szybko się wydrukowało. (Znów opisywane zachowanie jest bardzo subtelne)
Trevor Boyd Smith

59

Przyjęta odpowiedź nie działa dla mnie, a ponadto jest myląca i zmienia plik dziennika.

Używam czegoś takiego:

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

Jeśli wiersz dziennika pasuje do wzorca, zabij tailstartowany przez ten skrypt.

Uwaga: jeśli chcesz również wyświetlić dane wyjściowe na ekranie, albo | tee /dev/ttywypowiedz linię przed testowaniem w pętli while.


2
Działa to, ale pkillnie jest określone przez POSIX i nie jest dostępne wszędzie.
Richard Hansen

2
Nie potrzebujesz pętli while. użyj watch z opcją -g i możesz oszczędzić sobie nieprzyjemnego polecenia pkill.
l1zard

@ l1zard Czy potrafisz to zrealizować? Jak obserwowałbyś ogon pliku dziennika, dopóki nie pojawi się konkretna linia? (Mniej ważne, ale jestem również ciekawy, kiedy dodano zegarek -g; mam nowszy serwer Debian z tą opcją i inny stary bez RHEL).
Rob Whelan

Nie jest dla mnie jasne, dlaczego ogon jest tu potrzebny. O ile rozumiem tę poprawkę, użytkownik chce wykonać określone polecenie, gdy pojawi się określone słowo kluczowe w pliku dziennika. Polecenie podane poniżej przy użyciu funkcji watch wykonuje to zadanie.
l1zard

Niezupełnie - sprawdza, kiedy dany ciąg jest dodawany do pliku dziennika. Używam tego do sprawdzania, kiedy Tomcat lub JBoss jest w pełni uruchomiony; zapisują „Serwer uruchomiony” (lub podobny) za każdym razem, gdy to się dzieje.
Rob Whelan,

16

Jeśli używasz Bash (przynajmniej, ale wydaje się, że nie jest zdefiniowany przez POSIX, więc może brakować go w niektórych powłokach), możesz użyć składni

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

Działa prawie tak, jak wspomniane już rozwiązania FIFO, ale jest o wiele prostszy w pisaniu.


1
To działa, ale ogon nadal działa, dopóki nie wyślesz SIGTERM(Ctrl + C, zakończ komendę lub ją zabij)
mems

3
@mems, zrobi to każda dodatkowa linia w pliku dziennika. tailBędzie go odczytać, spróbuj wyjściu go, a następnie otrzymać SIGPIPE który będzie go rozwiązać. Więc w zasadzie masz rację; tailmoże działać w nieskończoność, jeśli nic nie zostanie kiedykolwiek ponownie zapisywane w pliku dziennika. W praktyce może to być bardzo miłe rozwiązanie dla wielu osób.
Alfe

14

Istnieje kilka sposobów tailna wyjście:

Słabe podejście: zmuszenie taildo napisania kolejnej linii

Możesz wymusić tailzapisanie innego wiersza wyników natychmiast po grepznalezieniu dopasowania i wyjściu. Spowoduje to, że taildostaniesz SIGPIPE, powodując jego wyjście. Jednym ze sposobów jest zmodyfikowanie monitorowanego pliku tailpo grepwyjściu.

Oto przykładowy kod:

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

W tym przykładzie catnie wyjdzie, dopóki grepnie zamknie standardowego wejścia , więc tailprawdopodobnie nie będzie w stanie pisać do potoku, zanim grepnie będzie miał możliwości zamknięcia standardowego wejścia. catsłuży do propagowania standardowego wyjścia grepniezmodyfikowanego.

To podejście jest stosunkowo proste, ale ma kilka wad:

  • Jeśli grepzamyka stdout przed zamknięciem stdin, zawsze wystąpi warunek wyścigu: grepzamyka stdout, wyzwala catwyjście, wyzwala echo, wyzwala tailwyjście linii. Jeśli ta linia została wysłana grepwcześniej grep, miała szansę na zamknięcie standardowego wejścia, tailnie dostanie tego SIGPIPEdopóki nie napisze innej linii.
  • Wymaga dostępu do zapisu do pliku dziennika.
  • Musisz być w porządku z modyfikowaniem pliku dziennika.
  • Możesz uszkodzić plik dziennika, jeśli zdarzy Ci się pisać w tym samym czasie co inny proces (zapisy mogą być przeplatane, co powoduje pojawienie się nowego wiersza w środku komunikatu dziennika).
  • To podejście jest specyficzne dla tail- nie będzie działać z innymi programami.
  • Trzeci etap rurociąg sprawia, że trudno jest uzyskać dostęp do kodu powrotu drugiego etapu gazociągu (o ile nie używasz rozszerzenia POSIX takich jak bash„s PIPESTATUStablicy). W tym przypadku nie jest to wielka sprawa, ponieważ grepzawsze zwróci 0, ale ogólnie etap środkowy może zostać zastąpiony innym poleceniem, którego kod powrotu jest dla Ciebie ważny (np. Coś, co zwraca 0 po wykryciu „uruchomienia serwera”, 1 gdy zostanie wykryty „serwer nie uruchomił się”).

Kolejne podejścia pozwalają uniknąć tych ograniczeń.

Lepsze podejście: unikaj rurociągów

Możesz użyć FIFO, aby całkowicie uniknąć potoku, umożliwiając kontynuowanie wykonywania po greppowrocie. Na przykład:

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}"

Linie oznaczone komentarzem # optionalmożna usunąć, a program będzie nadal działał; tailpozostanie po prostu, dopóki nie przeczyta innej linii danych wejściowych lub nie zostanie zabity przez inny proces.

Zaletami tego podejścia są:

  • nie musisz modyfikować pliku dziennika
  • podejście działa również w przypadku innych narzędzi tail
  • nie cierpi z powodu wyścigu
  • możesz łatwo uzyskać wartość zwracaną grep(lub dowolne alternatywne polecenie, którego używasz)

Wadą tego podejścia jest złożoność, zwłaszcza zarządzanie FIFO: Musisz bezpiecznie wygenerować tymczasową nazwę pliku i musisz upewnić się, że tymczasowa FIFO zostanie usunięta, nawet jeśli użytkownik kliknie Ctrl-C w środku scenariusz. Można to zrobić za pomocą pułapki.

Alternatywne podejście: Wyślij wiadomość do zabicia tail

Możesz dostać tailetap rurociągu do wyjścia, wysyłając mu podobny sygnał SIGTERM. Wyzwaniem jest niezawodna znajomość dwóch rzeczy w tym samym miejscu w kodzie: tailPID i czy grepzakończyło się.

Przy pomocy takiego potoku tail -f ... | grep ...można łatwo zmodyfikować pierwszy etap rurociągu, aby zapisać tailPID w zmiennej poprzez tworzenie tła taili czytanie $!. Łatwo jest również zmodyfikować drugi etap rurociągu, aby działał killpo grepwyjściu. Problem polega na tym, że dwa etapy potoku działają w osobnych „środowiskach wykonawczych” (w terminologii standardu POSIX), więc drugi etap potoku nie może odczytać żadnych zmiennych ustawionych przez pierwszy etap potoku. Bez użycia zmiennych powłoki albo drugi etap musi jakoś ustalić tailPID, aby mógł zabić tailpo greppowrocie, lub pierwszy etap musi zostać w jakiś sposób powiadomiony o greppowrocie.

Drugi etap może posłużyć pgrepdo uzyskania tailPID, ale byłoby to niewiarygodne (możesz dopasować niewłaściwy proces) i nieprzenośne ( pgrepnie jest określone w standardzie POSIX).

Pierwszy stopień może wysłać PID do drugiego stopnia przez rurę, echowprowadzając PID, ale ten ciąg zostanie pomieszany z tailwyjściem. Demultipleksowanie tych dwóch może wymagać złożonego schematu ucieczki, w zależności od wyjścia tail.

Możesz użyć FIFO, aby drugi etap rurociągu powiadomił pierwszy etap rurociągu o grepwyjściu. Wtedy pierwszy etap może zabić tail. Oto przykładowy kod:

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}"

To podejście ma wszystkie zalety i wady poprzedniego podejścia, z tym że jest bardziej skomplikowane.

Ostrzeżenie o buforowaniu

POSIX pozwala na pełne buforowanie strumieni stdin i stdout, co oznacza, że taildane wyjściowe mogą nie być przetwarzane przez grepdowolnie długi czas. W systemach GNU nie powinno być żadnych problemów: GNU grepużywa read(), co pozwala uniknąć buforowania, a GNU tail -fregularnie wywołuje fflush(), pisząc na standardowe wyjście. Systemy inne niż GNU mogą musieć zrobić coś specjalnego, aby wyłączyć lub regularnie opróżniać bufory.


Twoje rozwiązanie (podobnie jak inni, nie będę cię winić) będzie brakowało rzeczy, które zostały już zapisane w pliku dziennika przed rozpoczęciem monitorowania. tail -fTylko wyjście ostatnie dziesięć wierszy, a następnie wszystko dodaje. Aby to poprawić, możesz dodać opcję -n 10000do ogona, aby wydano również ostatnie 10000 linii.
Alfe

Inny pomysł: Twoje rozwiązanie FIFO może być wyprostowane, myślę, przekazując wyjście tail -fpoprzez FIFO i grepping na nim: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe

@Alfe: Mogę się mylić, ale wierzę, że tail -f logpisanie do FIFO spowoduje, że niektóre systemy (np. GNU / Linux) będą używać buforowania blokowego zamiast buforowania liniowego, co oznacza, grepże może nie widzieć pasującej linii, gdy pojawia się w dzienniku. System może udostępnić narzędzie do zmiany buforowania, na przykład stdbufz coreutils GNU. Takie narzędzie byłoby jednak nieprzenośne.
Richard Hansen

1
@Alfe: Właściwie wygląda na to, że POSIX nie mówi nic o buforowaniu, z wyjątkiem interakcji z terminalem, więc z perspektywy standardowej uważam, że twoje prostsze rozwiązanie jest równie dobre jak moje złożone. Nie jestem jednak w 100% pewien, jak różne implementacje zachowują się w każdym przypadku.
Richard Hansen

Właściwie, teraz wybieram jeszcze prostsze grep -q -m 1 trigger <(tail -f log)proponowane gdzie indziej i żyję z faktem, że tailbiegnie o jedną linię dłużej w tle, niż to konieczne.
Alfe

9

Pozwól mi rozwinąć odpowiedź na @ 00prometheus (która jest najlepsza).

Może powinieneś skorzystać z limitu czasu zamiast czekać w nieskończoność.

Poniższa funkcja bash będzie blokować do momentu pojawienia się wyszukiwanego terminu lub osiągnięcia określonego limitu czasu.

Status wyjścia wyniesie 0, jeśli ciąg zostanie znaleziony w limicie czasu.

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
}

Być może plik dziennika jeszcze nie istnieje tuż po uruchomieniu serwera. W takim przypadku należy zaczekać na jego pojawienie się przed wyszukiwaniem ciągu:

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))
}

Oto jak możesz go użyć:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"

Gdzie jest więc timeoutpolecenie?
ayanamist,

W rzeczywistości używanie timeoutjest jedynym niezawodnym sposobem, aby nie zawiesić się w nieskończoność, czekając na serwer, który nie może się uruchomić i już się zakończył.
gluk47

1
Ta odpowiedź jest najlepsza. Wystarczy skopiować funkcję i nazwać ją, jest bardzo łatwa i wielokrotnego użytku
Hristo Vrigazov

6

Po przeprowadzeniu testów znalazłem szybki 1-liniowy sposób, aby to zadziałało. Wygląda na to, że tail -f przestanie działać, gdy grep zakończy pracę, ale jest pewien haczyk. Wydaje się, że jest uruchamiany tylko wtedy, gdy plik jest otwarty i zamknięty. Osiągnąłem to, dodając pusty ciąg do pliku, gdy grep znajdzie dopasowanie.

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

Nie jestem pewien, dlaczego otwieranie / zamykanie pliku powoduje, że ogon zdaje sobie sprawę, że potok jest zamknięty, więc nie polegałbym na tym zachowaniu. ale wydaje się, że na razie działa.

Powód, dla którego się zamyka, spójrz na flagę -F, w porównaniu z flagą -f.


1
Działa to, ponieważ dołączenie do pliku dziennika powoduje tailwyświetlenie innego wiersza, ale do tego grepczasu zostało ono zakończone (prawdopodobnie - istnieje warunek wyścigu). Jeśli grepopuścił do tego czasu tailpisze inną linię, taildostanie SIGPIPE. To powoduje tailnatychmiastowe wyjście.
Richard Hansen

1
Wady tego podejścia: (1) istnieje warunek wyścigu (nie zawsze może wyjść natychmiast) (2) wymaga dostępu do zapisu do pliku dziennika (3) musisz być w porządku z modyfikacją pliku dziennika (4) możesz uszkodzić plik dziennika (5) działa tylko w przypadku tail(6), którego nie można łatwo dostosować, aby zachowywał się inaczej w zależności od różnych dopasowań ciągów („uruchomienie serwera” vs. „uruchomienie serwera nie powiodło się”), ponieważ nie można łatwo uzyskać kodu powrotu środkowego etapu rurociągu. Istnieje alternatywne podejście, które pozwala uniknąć tych wszystkich problemów - patrz moja odpowiedź.
Richard Hansen

6

Obecnie, jak podano, wszystkie przedstawione tail -ftutaj rozwiązania wiążą się z ryzykiem pobrania wcześniej zarejestrowanej linii „Server Started” (co może, ale nie musi stanowić problemu w konkretnym przypadku, w zależności od liczby zalogowanych linii i rotacji pliku dziennika / obcięcie).

Zamiast nadmiernie komplikować rzeczy, po prostu użyj mądrzejszego tail, jak pokazał bmike z snajpatem Perla. Najprostszym rozwiązaniem jest to retail, które ma zintegrowaną obsługę regex ze startu i zatrzymania wzory warunek:

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

Będzie to postępować zgodnie z plikiem jak normalnie, tail -fdopóki nie pojawi się pierwsza nowa instancja tego ciągu, a następnie wyjście. ( -uOpcja nie uruchamia się na istniejących liniach w ostatnich 10 liniach pliku, gdy jest w normalnym trybie „podążania”).


Jeśli używasz GNU tail(z coreutils ), następną najprostszą opcją jest użycie --pidi FIFO (nazwany potok):

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

Stosuje się FIFO, ponieważ procesy muszą być uruchamiane osobno, aby uzyskać i przekazać PID. Kolejka nadal cierpi z tego samego problemu kręci się terminowego zapisu powoduje tailotrzymywać na SIGPIPE , użyj --pidopcji tak, że tailwychodzi, gdy spostrzega, że grepzostał zakończony (konwencjonalnie stosowany do monitorowania pisarz proces zamiast czytnika , ale tailnie robi” to naprawdę obchodzi). Opcja -n 0służy do tailtego, aby stare linie nie wyzwalały dopasowania.


Na koniec możesz użyć stanowego ogona , który zapisze bieżące przesunięcie pliku, więc kolejne wywołania pokażą tylko nowe linie (obsługuje także rotację plików). W tym przykładzie użyto starego FWTK retail*:

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

* Uwaga, ta sama nazwa, inny program niż poprzednia opcja.

Zamiast mieć pętlę uruchamiania procesora, porównaj znacznik czasu pliku z plikiem stanu ( .${LOGFILE}.off) i uśpienie. Użyj „ -T”, aby określić lokalizację pliku stanu, jeśli jest to wymagane, powyższe zakłada bieżący katalog. Pomiń ten warunek lub w systemie Linux możesz inotifywaitzamiast tego użyć bardziej wydajnego :

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done

Czy mogę połączyć retailz limitem czasu, na przykład: „Jeśli upłynęło 120 sekund, a sprzedawca nadal nie czyta wiersza, podaj kod błędu i wyjdź ze sklepu”?
kiltek

@kiltek użyj GNU timeout(coreutils) do uruchomienia retaili po prostu sprawdź kod wyjścia 124 timeoutpo upływie limitu czasu ( zabije dowolną komendę, której użyjesz, aby uruchomić po ustalonym czasie)
mr.spuratic

4

Będzie to nieco trudne, ponieważ będziesz musiał wejść w kontrolę procesu i sygnalizację. Więcej kludgey byłoby rozwiązaniem dwóch skryptów wykorzystującym śledzenie PID. Lepiej byłoby użyć nazwanych potoków w ten sposób.

Jakiego skryptu powłoki używasz?

Dla szybkiego i brudnego rozwiązania dla jednego skryptu - zrobiłbym skrypt w Perlu za pomocą File: Tail

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

Zamiast drukować wewnątrz pętli while, możesz filtrować pod kątem dopasowania łańcucha i zerwać z pętli while, aby skrypt mógł kontynuować.

Każdy z nich powinien wymagać jedynie odrobiny nauki implementacji kontroli przepływu, której szukasz.


za pomocą bash. mój perl-fu nie jest tak silny, ale dam temu szansę.
Alex Hofsteede

Używaj rur - uwielbiają bash i bash je uwielbiają. (a twoje oprogramowanie do tworzenia kopii zapasowych będzie cię szanować, gdy trafi w jedną z twoich rur)
bmike

maxinterval=>300oznacza, że ​​będzie sprawdzał plik co pięć minut. Ponieważ wiem, że moja linia pojawi się w pliku na chwilę, używam znacznie bardziej agresywnego sondowania:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller

2

poczekaj na pojawienie się pliku

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

poczekaj, aż łańcuch pojawi się w pliku

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

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


2
To sondowanie ma dwie główne wady: 1. Marnuje czas obliczeń przez ciągłe przeglądanie dziennika. Zastanów się, /path/to/the.filektóry jest 1,4 GB duży; to jest jasne, że to jest problem. 2. Czeka dłużej niż to konieczne, gdy pojawi się wpis w dzienniku, w najgorszym przypadku 10s.
Alfe

2

Nie mogę sobie wyobrazić czystszego rozwiązania niż to:

#!/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, może nazwa może ulec poprawie ...

Zalety:

  • nie używa żadnych specjalnych narzędzi
  • nie zapisuje na dysk
  • z wdziękiem opuszcza ogon i zamyka fajkę
  • jest dość krótki i łatwy do zrozumienia

2

Aby to zrobić, nie potrzebujesz ogona. Myślę, że zegarek komenda jest to, czego szukasz. Polecenie watch monitoruje wyjście pliku i może zostać zakończone opcją -g , gdy dane wyjściowe ulegną zmianie.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction

1
Ponieważ działa to raz na dwie sekundy, nie kończy się natychmiast po pojawieniu się wiersza w pliku dziennika. Ponadto nie działa dobrze, jeśli plik dziennika jest bardzo duży.
Richard Hansen


1

Alex, myślę, że ten bardzo ci pomoże.

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

to polecenie nigdy nie spowoduje wpisu w pliku dziennika, ale będzie cicho grep ...


1
To nie zadziała - musisz dołączyć, w logfileprzeciwnym razie może upłynąć dowolny czas, zanim tailwyjdzie kolejna linia i wykryje, że grepumarł (przez SIGPIPE).
Richard Hansen

1

Oto znacznie lepsze rozwiązanie, które nie wymaga zapisywania do pliku dziennika, co w niektórych przypadkach jest bardzo niebezpieczne, a nawet niemożliwe.

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

Obecnie ma tylko jeden efekt uboczny, tailproces pozostanie w tle do momentu zapisania następnego wiersza w dzienniku.


tail -n +0 -fzaczyna się od początku pliku. tail -n 0 -fzaczyna się od końca pliku.
Stephen Ostermiller

1
Kolejny efekt uboczny otrzymuję:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller

Uważam, że w tej odpowiedzi „następna lista” powinna być „następną linią”.
Stephen Ostermiller

To działa, ale tailproces pozostaje uruchomiony w tle.
cbaldan

1

Inne rozwiązania tutaj mają kilka problemów:

  • jeśli proces rejestrowania jest już wyłączony lub spada podczas pętli, będą one działać w nieskończoność
  • edycja dziennika, który powinien być tylko przeglądany
  • niepotrzebnie zapisując dodatkowy plik
  • nie dopuszczając dodatkowej logiki

Oto, co wymyśliłem, używając przykładu tomcat (usuń skróty, jeśli chcesz zobaczyć dziennik podczas jego uruchamiania):

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"
}

1

tailPolecenie może są umieszczane w tle i jego pid echem w greppodpowłoce. W greppodpowłoce moduł obsługi pułapki na EXIT może zabić tailpolecenie.

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

1

Przeczytaj je wszystkie. tldr: oddzielić zakończenie ogona od grep.

Dwie najwygodniejsze formy to

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

a jeśli masz bash

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

Ale jeśli przeszkadza ci siedzący w tle ogon, istnieje lepszy sposób niż fifo lub jakakolwiek inna odpowiedź tutaj. Wymaga uderzenia.

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

Lub jeśli to nie ogon wytwarza rzeczy,

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

0

Spróbuj użyć inotify (inotifywait)

Ustawiasz inotifywait dla każdej zmiany pliku, a następnie sprawdzasz plik za pomocą grep, jeśli nie znaleziono, po prostu uruchom ponownie inotifywait, jeśli znaleziono, zamknij pętlę ...


W ten sposób cały plik musiałby być sprawdzany za każdym razem, gdy coś do niego jest zapisywane. Nie działa dobrze w przypadku plików dziennika.
grawity

1
Innym sposobem jest utworzenie dwóch skryptów: 1. tail -f logfile.log | grep -m 1 „Server Started”> / tmp / found 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / znaleziono; zabij -KILL - $ MYPID
Evengard

Chciałbym, abyś edytował swoją odpowiedź, aby pokazać przechwytywanie PID, a następnie użycie inotifywait - eleganckiego rozwiązania, które byłoby łatwe do zrozumienia dla kogoś, kto przywykł do grep, ale potrzebuje bardziej wyrafinowanego narzędzia.
bmike

PID tego, co chciałbyś uchwycić? Mogę spróbować, jeśli wyjaśnisz trochę więcej, czego chcesz
Evengard,

0

Chcesz wyjść zaraz po napisaniu wiersza, ale chcesz też wyjść po upływie limitu czasu:

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

-2

co powiesz na to:

chociaż prawda; zrobić, jeśli [! -z $ (grep „myRegEx” myLog.log)]; następnie złam; fi; gotowy

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.