Chciałbym dołączyć znacznik czasu do każdego wiersza wyniku polecenia. Na przykład:
foo bar baz
stałoby się
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… gdzie teraz przedrostek to czas, w którym linia została wydrukowana. Jak mogę to osiągnąć?
Komentarze
- Czy istnieje narzędzie Unix do dołączania znaczników czasu przed stdin?
Odpowiedź
moreutils zawiera ts
, co robi to całkiem nieźle:
command | ts "[%Y-%m-%d %H:%M:%S]"
To eliminuje również potrzebę pętli, każda linia wyjścia będzie miała nałożony znacznik czasu.
$ echo -e "foo\nbar\nbaz" | ts "[%Y-%m-%d %H:%M:%S]" [2011-12-13 22:07:03] foo [2011-12-13 22:07:03] bar [2011-12-13 22:07:03] baz
Chcesz wiedzieć, kiedy ten serwer się zresetował. Po prostu uruchom ping | ts
, problem rozwiązany: D.
Komentarze
- Skąd o tym nie wiedziałem ?!?!?! To niesamowicie uzupełnia tail -f!
tail -f /tmp/script.results.txt | ts
- Jeśli nie ' nie mam polecenia ts, czego mam użyć?
- Jeśli ' nie działa, spróbuj przekierować stderr na standardowe wyjście, np.
ssh -v 127.0.0.1 2>&1 | ts
- Zainstaluj, wykonując
sudo apt install moreutils
na Debianie iyum install moreutils
na Fedora. - Myślę, że wskazanie parametru
-s
jest przydatne. Ponieważ wyświetla czas wykonywania polecenia. Osobiście lubię używać zarównots
, jak its -s
w tym samym czasie. Wygląda mniej więcej tak:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Spowoduje to dodanie wierszy dziennika w następujący sposób:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Odpowiedź
Po pierwsze, jeśli oczekujesz, że te znaczniki czasu faktycznie reprezentują zdarzenie, pamiętaj, że ponieważ wiele programów wykonuje buforowanie linii (niektóre bardziej agresywnie niż inne), ważne jest, aby myśleć o tym jako o czasie zbliżonym do czasu, w którym oryginalna linia zostały wydrukowane zamiast sygnatury czasowej wykonywanej czynności.
Możesz również chcieć sprawdzić, czy Twoje polecenie nie ma wbudowanej funkcji przeznaczonej do tego. Na przykład ping -D
istnieje w niektórych ping
wersjach i wypisuje czas od epoki Uniksa przed każdą linią. Jeśli jednak polecenie nie zawiera własnej metody, jest kilka metod i narzędzi, które można zastosować, między innymi:
Powłoka POSIX
Pamiętaj, że ponieważ wiele powłok przechowuje swoje łańcuchy wewnętrznie jako cstrings, jeśli wejście zawiera wartość null zwęglać acter (\0
), może to spowodować przedwczesne zakończenie linii.
command | while IFS= read -r line; do printf "[%s] %s\n" "$(date "+%Y-%m-%d %H:%M:%S")" "$line"; done
GNU awk
command | gawk "{ print strftime("[%Y-%m-%d %H:%M:%S]"), $0 }"
Perl
command | perl -pe "use POSIX strftime; print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
Python
command | python -c "import sys,time;sys.stdout.write("".join(( " ".join((time.strftime("[%Y-%m-%d %H:%M:%S]", time.localtime()), line)) for line in sys.stdin )))"
Ruby
command | ruby -pe "print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")"
Komentarze
- Problem polega na tym, że wiele programów się włącza jeszcze większe buforowanie wyjścia, gdy ich standardowe wyjście jest potokiem zamiast terminalem.
- @cjm – True. Część buforowania danych wyjściowych można złagodzić, używając
stdbuf -o 0
, ale jeśli program ręcznie obsługuje buforowanie wyjścia, wygrał ' t pomoc (chyba że istnieje opcja wyłączenia / zmniejszenia rozmiaru bufora wyjściowego). - W przypadku Pythona możesz wyłączyć buforowanie wierszy za pomocą
python -u
- @Bwmat No.
... for x in sys.stdin
iteruje po wierszach bez buforowania ich wszystkich w pamięci. - Zrób to, a otrzymasz buforowanie … za 1 1 1 1 1; śpij 1; Echo; gotowe | python -c ' import sys, time; sys.stdout.write (” ” .join ((” ” .join ((time.strftime (” [% Y-% m-% d% H:% M:% S] „, time.gmtime ()), line)) dla linii w sys.stdin))) '
Odpowiedź
W przypadku pomiaru różnicy linia po linii , spróbuj gnomon .
Jest to narzędzie wiersza poleceń, trochę podobne do moreutils „s ts, aby dołączyć informacje o sygnaturze czasowej do standardowego wyjścia innego polecenia. Przydatne w przypadku długotrwałych procesów, w których chciałbyś mieć historyczny zapis tego, co tak długo trwa.
Przekierowanie czegokolwiek do gnomona spowoduje dołącz przed sobą znacznik czasu do każdej linii, wskazując, jak długo ta linia była ostatnią linią w buforze – to znaczy, ile czasu zajęło pojawienie się następnej linii. Domyślnie gnomon wyświetla sekundy, które upłynęły między każdą linią, ale można to skonfigurować.
Komentarze
- Wygląda na świetną alternatywę dla
ts
podczas korzystania z aktywnych procesów. Chociażts
lepiej nadaje się do procesów nieinteraktywnych.
Odpowiedź
Wolałbym skomentować powyżej, ale nie mogę, z reputacji. W każdym razie powyższa próbka Perla może być niebuforowana w następujący sposób:
command | perl -pe "use POSIX strftime; $|=1; select((select(STDERR), $| = 1)[0]); print strftime "[%Y-%m-%d %H:%M:%S] ", localtime"
pierwszy „$ |” unbuforuje STDOUT. Drugi ustawia stderr jako bieżący domyślny kanał wyjściowy i usuwa go z bufora. Ponieważ select zwraca oryginalne ustawienie $ |, zawijając zaznaczenie wewnątrz select, resetujemy również $ | do wartości domyślnych, STDOUT.
I tak, możesz wyciąć „i wkleić” tak, jak jest. Napisałem go w wielu wierszach dla czytelności.
A jeśli naprawdę chcesz uzyskać dokładność (i masz zainstalowany Time :: Hires ):
command | perl -pe "use POSIX strftime; use Time::HiRes gettimeofday; $|=1; select((select(STDERR), $| = 1)[0]); ($s,$ms)=gettimeofday(); $ms=substr(q(000000) . $ms,-6); print strftime "[%Y-%m-%d %H:%M:%S.$ms]", localtime($s)"
Komentarze
- Działa jak urok, bez konieczności instalowania jakichkolwiek niestandardowych pakietów.
Odpowiedź
Post Ryana dostarcza interesującego pomysłu, jednak pod wieloma względami zawodzi. Podczas testowania z tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
zauważyłem, że sygnatura czasowa pozostaje taka sama, nawet jeśli stdout
pojawia się później z różnicą w sekundach. Rozważ to wyjście:
[2016-07-14 01:44:25] Jul 14 01:44:32 eagle dhclient[16091]: DHCPREQUEST of 192.168.0.78 on wlan7 to 255.255.255.255 port 67 (xid=0x411b8c21) [2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: Joining mDNS multicast group on interface wlan7.IPv6 with address fe80::d253:49ff:fe3d:53fd. [2016-07-14 01:44:25] Jul 14 01:44:34 eagle avahi-daemon[740]: New relevant interface wlan7.IPv6 for mDNS.
Moje proponowane rozwiązanie jest podobne, jednak zapewnia właściwe oznaczanie czasu i używa nieco bardziej przenośnego printf
zamiast echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Dlaczego bash -c "..." bash
? Ponieważ z powodu opcji -c
, najpierw argument nt zostanie przypisany do $0
i nie pojawi się w wynikach. Sprawdź stronę podręcznika swojej powłoki, aby znaleźć właściwy opis -c
Testowanie tego rozwiązania z tail -f /var/log/syslog
i (jako pewnie można się domyślić) odłączenie i ponowne połączenie z moim Wi-Fi, pokazało prawidłowe oznaczenie czasu zapewniane przez zarówno date
, jak i syslog
wiadomości
Bash można zastąpić dowolną powłoką podobną do Bournea, można to zrobić za pomocą ksh
lub dash
, przynajmniej tych które mają opcję -c
.
Potencjalne problemy:
Rozwiązanie wymaga xargs
, który jest dostępny w systemach zgodnych z POSIX, więc większość systemów uniksopodobnych powinna być uwzględniona. Oczywiście nie będzie działać, jeśli twój system nie jest zgodny z POSIX lub nie ma GNU findutils
Odpowiedź
Większość odpowiedzi sugeruje użycie date
, ale jest to wystarczająco wolne . Jeśli twoja wersja basha jest nowsza niż 4.2.0, lepiej jest użyć zamiast tego printf
, jest to wbudowany bash. Jeśli potrzebujesz obsługiwać starsze wersje bash, możesz utworzyć funkcję log
zależną od wersji basha:
TIMESTAMP_FORMAT="%Y-%m-%dT%H:%M:%S" # Bash version in numbers like 4003046, where 4 is major version, 003 is minor, 046 is subminor. printf -v BV "%d%03d%03d" ${BASH_VERSINFO[0]} ${BASH_VERSINFO[1]} ${BASH_VERSINFO[2]} if ((BV > 4002000)); then log() { ## Fast (builtin) but sec is min sample for most implementations printf "%(${TIMESTAMP_FORMAT})T %5d %s\n" "-1" $$ "$*" # %b convert escapes, %s print as is } else log() { ## Slow (subshell, date) but support nanoseconds and legacy bash versions echo "$(date +"${TIMESTAMP_FORMAT}") $$ $*" } fi
Zobacz prędkość różnice:
user@host:~$time for i in {1..10000}; do printf "%(${TIMESTAMP_FORMAT})T %s\n" "-1" "Some text" >/dev/null; done real 0m0.410s user 0m0.272s sys 0m0.096s user@host:~$time for i in {1..10000}; do echo "$(date +"${TIMESTAMP_FORMAT}") Some text" >/dev/null; done real 0m27.377s user 0m1.404s sys 0m5.432s
UPD: zamiast $(date +"${TIMESTAMP_FORMAT}")
lepiej jest użyć $(exec date +"${TIMESTAMP_FORMAT}")
lub nawet $(exec -c date +"${TIMESTAMP_FORMAT}")
zbyt przyspieszenie wykonania.
UPD2: bash 5 zapewnia zmienną EPOCHREALTIME
na mikrosekundy szczegółowości, możesz go użyć za pomocą tego polecenia (około 30% wolniej niż tylko sekundy): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Komentarze
- Ta odpowiedź jest bardzo niedoceniana! Szczególnie miło jest pokazać względny wpływ na wydajność.
- Doskonała sugestia, ale niestety data nie zapewnia rozdzielczości poniżej sekundy, co jest wymagane w moim przypadku
- W bashu 5.0+ możesz użyć tego polecenia:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Odpowiedz
Możesz to zrobić za pomocą date
i xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Wyjaśnienie:
xargs -L 1
mówi xargs, aby wykonywał polecenie kontynuacji dla każdej 1 linii wejścia i przechodzi w pierwszym wierszu, gdy to robi. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
w zasadzie powtarza datę z argumentem wejściowym na końcu
Komentarze
- Rozwiązanie jest blisko, ale nie ' t sygnatura czasowa poprawnie, jeśli chodzi o dane wyjściowe oddzielone długimi okresami czasu. Ponadto ' używasz odwrotnych apostrofów i nie ' nie cytowano
$1
. To ' nie jest w dobrym stylu. Zawsze cytuj zmienne. Ponadto ' używaszecho
, który nie jest przenośny. ' jest w porządku, ale może nie działać poprawnie na niektórych systemach. - Po przetestowaniu tego wygląda na to, że ' masz całkowitą rację … czy znasz jakiś sposób, aby
date
przewartościować każdą linię, czy jest to prawie beznadziejne?
Odpowiedz
Bezwstydna wtyczka do czegoś, właśnie napisałem, aby rozwiązać ten konkretny problem: ets
, napisane w Go.
Na stronie projektu można znaleźć wiele przykładów użycia.
Istotną różnicą w stosunku do istniejących odpowiedzi i podobnych ofert jest to, że ets
zostało zaprojektowane tak, aby uruchamiać polecenie za Ciebie, w pty (pseudo tty) – to znaczy symulując twoje polecenie działa natywnie w tty. W porównaniu z wyjściem polecenia potokowego do np. ts
, to sprawia, że znaczniki czasu są przeważnie przezroczyste i rozwiązuje kilka problemów z potokami:
- Niektóre programy agresywnie buforują podczas zapisywania do potoku, więc nie widzę wyjścia, a potem całą masę danych wyjściowych (tak, możesz je stdbuf, możesz nawet zawijać stdbuf i ts w alias / funkcję, ale nie byłoby lepiej, gdyby coś działało po wyjęciu z pudełka);
- Niektóre programy wyłączają kolor i / lub interaktywność podczas pisania do potoku;
- Status wyjścia zniknął, chyba że włączyłeś pipefail; itp.
Polecenia mogą być bezpośrednio wykonywane „ed, co oznacza, że możesz po prostu wstawić ets
do istniejącego wiersza poleceń lub mogą to być polecenia powłoki (jak pokazano na powyższym gifie). Oczywiście, jeśli chcesz potokować wyjście, ets
też możesz to zrobić.
ets
obsługuje to samo tryby sygnatury czasowej jako moreutils ts
: tryb czasu bezwzględnego, tryb upływu czasu i tryb czasu przyrostowego. Używa rozsądniejszych ustawień domyślnych (np. Zegar monotoniczny jest zawsze używany dla upływających / przyrostowych znaczników czasu) i ma dodatkowe wsparcie dla niestandardowych stref czasowych. Oto szczegółowe porównanie tutaj .
Ponownie, https://github.com/zmwangx/ets . Spróbuj, zgłoś błędy itp.
Odpowiedz
Umieść to blisko początku skryptu
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
Lub, aby uzyskać dodatkowy kredyt, wyślij do pliku dziennika za pośrednictwem:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) echo "note: redirecting output to [${log_file}]" exec &> >( ts "%Y-%m-%d %H:%M:%.S " > ${log_file} )
do wyjścia zarówno do konsoli, jak i do pliku dziennika:
script_name=foobar log_file=$( printf "/tmp/${script_name}-%(%Y-%m-%d)T.%(%H%M%S)T.log" -1 ) exec &> >( ts "%Y-%m-%d %H:%M:%.S " | tee ${log_file} )
Główną zaletą tego rozwiązania jest oddzielenie logowania od wszystkiego innego, a nie zaśmiecanie treści skryptu przez przekierowanie do tee
lub czegoś podobnego przy każdym poleceniu, bez konieczności pisania własnych funkcji rejestrowania i trzymania się zwykłych starych echo
.
Program ts
znajduje się w pakiecie moreutils
, który powinien być łatwo dostępny pod każdym rozsądnym rozsądny system zarządzania. 🙂