Istnieje kilka sposobów tail
na wyjście:
Słabe podejście: zmuszenie tail
do napisania kolejnej linii
Możesz wymusić tail
zapisanie innego wiersza wyników natychmiast po grep
znalezieniu dopasowania i wyjściu. Spowoduje to, że tail
dostaniesz SIGPIPE
, powodując jego wyjście. Jednym ze sposobów jest zmodyfikowanie monitorowanego pliku tail
po grep
wyjściu.
Oto przykładowy kod:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
W tym przykładzie cat
nie wyjdzie, dopóki grep
nie zamknie standardowego wejścia , więc tail
prawdopodobnie nie będzie w stanie pisać do potoku, zanim grep
nie będzie miał możliwości zamknięcia standardowego wejścia. cat
służy do propagowania standardowego wyjścia grep
niezmodyfikowanego.
To podejście jest stosunkowo proste, ale ma kilka wad:
- Jeśli
grep
zamyka stdout przed zamknięciem stdin, zawsze wystąpi warunek wyścigu: grep
zamyka stdout, wyzwala cat
wyjście, wyzwala echo
, wyzwala tail
wyjście linii. Jeśli ta linia została wysłana grep
wcześniej grep
, miała szansę na zamknięcie standardowego wejścia, tail
nie dostanie tego SIGPIPE
dopó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 PIPESTATUS
tablicy). W tym przypadku nie jest to wielka sprawa, ponieważ grep
zawsze 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 grep
powrocie. 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 # optional
można usunąć, a program będzie nadal działał; tail
pozostanie 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ć tail
etap 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: tail
PID i czy grep
zakończyło się.
Przy pomocy takiego potoku tail -f ... | grep ...
można łatwo zmodyfikować pierwszy etap rurociągu, aby zapisać tail
PID w zmiennej poprzez tworzenie tła tail
i czytanie $!
. Łatwo jest również zmodyfikować drugi etap rurociągu, aby działał kill
po grep
wyjś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ć tail
PID, aby mógł zabić tail
po grep
powrocie, lub pierwszy etap musi zostać w jakiś sposób powiadomiony o grep
powrocie.
Drugi etap może posłużyć pgrep
do uzyskania tail
PID, ale byłoby to niewiarygodne (możesz dopasować niewłaściwy proces) i nieprzenośne ( pgrep
nie jest określone w standardzie POSIX).
Pierwszy stopień może wysłać PID do drugiego stopnia przez rurę, echo
wprowadzając PID, ale ten ciąg zostanie pomieszany z tail
wyjś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 grep
wyjś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 tail
dane wyjściowe mogą nie być przetwarzane przez grep
dowolnie długi czas. W systemach GNU nie powinno być żadnych problemów: GNU grep
używa read()
, co pozwala uniknąć buforowania, a GNU tail -f
regularnie 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.