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.