Dołączanie znacznika czasu do każdej linii wyjścia z polecenia

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

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 i yum install moreutils na Fedora.
  • Myślę, że wskazanie parametru -s jest przydatne. Ponieważ wyświetla czas wykonywania polecenia. Osobiście lubię używać zarówno ts, jak i ts -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ć.

demo gnomonu

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żywasz echo, 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.

demo

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. 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *