Odpowiedzi:
$ cat input.log | sed -e "s/^/$(date -R) /" >> output.log
Jak to działa:
cat
odczytuje nazwany plik input.log
i po prostu drukuje go do standardowego strumienia wyjściowego.
Zwykle standardowe wyjście jest podłączone do terminala, ale ten mały skrypt zawiera, |
więc powłoka przekierowuje standardowe wyjście cat
na standardowe wejście sed
.
sed
odczytuje dane (jak cat
je produkuje), przetwarza (zgodnie ze skryptem dostarczonym z -e
opcją), a następnie drukuje na standardowe wyjście. Skrypt "s/^/$(date -R) /"
oznacza zamianę każdego początku wiersza na tekst generowany przez date -R
komendę (ogólna konstrukcja komendy replace to:) s/pattern/replace/
.
Następnie zgodnie z >>
bash
przekierowuje wyjście sed
do pliku o nazwie output.log
( >
oznacza zastąpić zawartość pliku i >>
oznacza dopisać na końcu).
Problem jest $(date -R)
oceniany raz po uruchomieniu skryptu, dzięki czemu wstawi on aktualny znacznik czasu na początku każdej linii. Aktualny znacznik czasu może być daleki od momentu wygenerowania wiadomości. Aby tego uniknąć, musisz przetwarzać wiadomości zapisywane do pliku, a nie zadanie cron.
Standardowe przekierowanie strumienia opisane powyżej zwane potokiem . Możesz przekierować go nie tylko |
między poleceniami w skrypcie, ale poprzez plik FIFO ( zwany potokiem ). Jeden program zapisuje do pliku, a inny odczytuje dane i odbiera je przy pierwszej wysyłce.
Wybierz przykład:
$ mkfifo foo.log.fifo
$ while true; do cat foo.log.fifo | sed -e "s/^/$(date -R) /" >> foo.log; done;
# have to open a second terminal at this point
$ echo "foo" > foo.log.fifo
$ echo "bar" > foo.log.fifo
$ echo "baz" > foo.log.fifo
$ cat foo.log
Tue, 20 Nov 2012 15:32:56 +0400 foo
Tue, 20 Nov 2012 15:33:27 +0400 bar
Tue, 20 Nov 2012 15:33:30 +0400 baz
Jak to działa:
mkfifo
tworzy nazwaną potok
while true; do sed ... ; done
uruchamia nieskończoną pętlę i przy każdej iteracji biegnie sed
z przekierowaniem foo.log.fifo
na standardowe wejście; sed
blokuje oczekiwanie na dane wejściowe, a następnie przetwarza otrzymaną wiadomość i drukuje ją na standardowe wyjście przekierowane na foo.log
.
W tym momencie musisz otworzyć nowe okno terminala, ponieważ pętla zajmuje aktualny terminal.
echo ... > foo.log.fifo
wypisuje komunikat na standardowe wyjście przekierowany do pliku fifo i sed
odbiera go oraz przetwarza i zapisuje do zwykłego pliku.
Ważną uwagą jest fifo, tak jak każda inna rura nie ma sensu, jeśli jedna z jej stron nie jest połączona z żadnym procesem. Jeśli spróbujesz zapisać na potoku, bieżący proces zostanie zablokowany, dopóki ktoś nie przeczyta danych po drugiej stronie potoku. Jeśli chcesz czytać z potoku, proces zostanie zablokowany, dopóki ktoś nie zapisze danych na potoku. sed
Pętla w powyższym przykładzie nie robi nic (śpi), dopóki nie robić echo
.
W konkretnej sytuacji wystarczy skonfigurować aplikację, aby zapisywała komunikaty dziennika w pliku FIFO. Jeśli nie możesz go skonfigurować - po prostu usuń oryginalny plik dziennika i utwórz plik FIFO. Ale zauważ ponownie, że jeśli sed
pętla umrze z jakiegoś powodu - twój program zostanie zablokowany przy próbie write
przejścia do pliku, dopóki ktoś nie zrobi tego read
z fifo.
Korzyścią jest ocena bieżącego znacznika czasu i dołączanie go do komunikatu, gdy program zapisuje go do pliku.
tailf
Aby zapisywanie w dzienniku i przetwarzanie było bardziej niezależne, możesz użyć dwóch zwykłych plików tailf
. Aplikacja zapisuje komunikat do nieprzetworzonego pliku, a inny proces odczytuje nowe wiersze (postępuje zgodnie z zapisem asynchronicznym) i przetwarza dane z zapisem do drugiego pliku.
Weźmy przykład:
# will occupy current shell
$ tailf -n0 bar.raw.log | while read line; do echo "$(date -R) $line" >> bar.log; done;
$ echo "foo" >> bar.raw.log
$ echo "bar" >> bar.raw.log
$ echo "baz" >> bar.raw.log
$ cat bar.log
Wed, 21 Nov 2012 16:15:33 +0400 foo
Wed, 21 Nov 2012 16:15:36 +0400 bar
Wed, 21 Nov 2012 16:15:39 +0400 baz
Jak to działa:
Uruchom tailf
proces, który nastąpi po zapisie bar.raw.log
i wydrukuj go na standardowe wyjście przekierowane do nieskończonej while read ... echo
pętli. Ta pętla wykonuje dwie czynności: odczytuje dane ze standardowego wejścia do zmiennej buforowej o nazwie, line
a następnie zapisuje wygenerowany znacznik czasu z następującymi buforowanymi danymi do bar.log
.
Napisz kilka wiadomości do bar.raw.log
. Musisz to zrobić w osobnym oknie terminala, ponieważ zostanie zajęte pierwsze, tailf
które będzie śledzić zapisy i wykonywać swoją pracę. Całkiem proste.
Zaletą jest to, że Twoja aplikacja nie zablokuje się, jeśli zabijesz tailf
. Wady to mniej dokładne znaczniki czasu i powielające się pliki dziennika.
tailf
, dodałem właściwy sposób korzystania z niego. Właściwie sposób z tailf
wydaje się być bardziej elegancki, ale opuściłem fifo z nadzieją, że przyda się komuś.
Możesz użyć ts
skryptu perla z moreutils
:
$ echo test | ts %F-%H:%M:%.S
2012-11-20-13:34:10.731562 test
Zmodyfikowano z odpowiedzi Dmitrija Wasiljanowa.
W skrypcie bash możesz przekierowywać i owijać dane wyjściowe znacznikiem czasu linia po linii w locie.
Kiedy użyć:
tailf
pliku dziennika, jak powiedział Dmitrij Wasiljanow.Przykład o nazwie foo.sh
:
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line" >> foo.log; done;)
echo "foo"
sleep 1
echo "bar" >&2
sleep 1
echo "foobar"
A wynik:
$ bash foo.sh
$ cat foo.log
May 12 20:04:11 foo
May 12 20:04:12 bar
May 12 20:04:13 foobar
Jak to działa
exec &>
Przekieruj stdout i stderr w to samo miejsce>( ... )
potokuje dane wyjściowe do asynchronicznego polecenia wewnętrznegoNa przykład:
znacznik czasu potoku i zaloguj się do pliku
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line" >> foo.log; done;)
echo "some script commands"
/path-to/some-thrid-party-programs
Lub wydrukuj sygnaturę czasową i zaloguj na standardowe wyjście
#!/bin/bash
exec &> >(while read line; do echo "$(date +'%h %d %H:%M:%S') $line"; done;)
echo "some script commands"
/path-to/some-thrid-party-programs
następnie zapisz je w /etc/crontab
ustawieniach
* * * * * root /path-to-script/foo.sh >> /path-to-log-file/foo.log
Użyłem ts
tego sposobu, aby uzyskać wpis ze znacznikiem czasu w dzienniku błędów skryptu, którego używam do wypełnienia kaktusów statystykami zdalnego hosta.
Aby przetestować kaktusy, używam rand
do dodania losowych wartości, których używam do wykresów temperatury do monitorowania temperatury mojego systemu.
Pushmonstats.sh to skrypt, który zbiera statystyki temperatury systemu z mojego komputera i wysyła je do Raspberry Pi, na którym działa Cacti. Jakiś czas temu sieć utknęła. W dzienniku błędów mam tylko przerwy SSH. Niestety, brak wpisów czasu w tym dzienniku. Nie wiedziałem, jak dodać znacznik czasu do wpisu w dzienniku. Po kilku wyszukiwaniach w Internecie natknąłem się na ten post i właśnie tego dokonałem ts
.
Aby to przetestować, użyłem nieznanej opcji rand
. Co dało błąd stderrowi. Aby go przechwycić, przekierowuję go do pliku tymczasowego. Następnie używam cat, aby wyświetlić zawartość pliku i przesłać do niego potokiem ts
, dodać format czasu, który znalazłem w tym poście, i na koniec zalogować go do pliku błędu. Następnie usuwam zawartość pliku tymczasowego, w przeciwnym razie otrzymuję podwójne wpisy dla tego samego błędu.
Crontab:
* * * * * /home/monusr/bin/pushmonstats.sh 1>> /home/monusr/pushmonstats.log 2> /home/monusr/.err;/bin/cat /home/monusr/.err|/usr/bin/ts %F-%H:%M:%.S 1>> /home/monusr/pushmonstats.err;> /home/monusr/.err
To daje następujące informacje w moim dzienniku błędów:
2014-03-22-19:17:53.823720 rand: unknown option -- '-l'
Może nie jest to zbyt elegancki sposób na zrobienie tego, ale działa. Zastanawiam się, czy istnieje bardziej eleganckie podejście.