Przygotowywanie znacznika czasu do każdego wiersza wyniku polecenia


Odpowiedzi:


274

moreutils obejmuje, tsco robi to całkiem ładnie:

command | ts '[%Y-%m-%d %H:%M:%S]'

Eliminuje to również potrzebę pętli, każda linia wyjścia będzie miała na sobie 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 wrócił, zrestartowałeś się? Po prostu uruchom ping | ts, problem rozwiązany: D.


8
Jak mogłem o tym nie wiedzieć?!?!?! Uzupełnia to ogon - niesamowicie! tail -f /tmp/script.results.txt | ts
Bruno Bronosky,

Co z cygwinem? Czy jest coś podobnego? Nie wygląda na to, że są tam jeszcze morele Joeya.
CrazyPenguin,

3
jeśli nie mam polecenia ts, czego powinienem użyć?
ekassis

1
Jeśli to nie działa, spróbuj przekierować stderr na stdout np.ssh -v 127.0.0.1 2>&1 | ts
jchook

3
Myślę, że wskazanie parametru -sjest przydatne. Jak to wyświetla środowisko uruchomieniowe polecenia. Ja osobiście lubię używać zarówno tsi ts -sw tym samym czasie. Wygląda tak: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. To poprzedza następujące wiersze dziennika:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone

100

Po pierwsze, jeśli oczekujesz, że te znaczniki czasu będą faktycznie reprezentować 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 tak blisko czasu, jaki miałby oryginalny wiersz został wydrukowany, a nie znacznik czasu akcji.

Możesz także sprawdzić, czy twoje polecenie nie ma jeszcze wbudowanej funkcji dedykowanej do tego. Na przykład ping -Distnieje w niektórych pingwersjach i wypisuje czas od epoki Uniksa przed każdą linią. Jeśli twoje polecenie nie zawiera własnej metody, istnieje kilka metod i narzędzi, które można zastosować, między innymi:

Powłoka POSIX

Pamiętaj, że ponieważ wiele powłok wewnętrznie zapisuje swoje łańcuchy jako łańcuchy, jeśli dane wejściowe zawierają znak null ( \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'

Pyton

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 )))'

Rubin

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'

3
Jednym z problemów jest to, że wiele programów włącza jeszcze więcej buforowania danych wyjściowych, gdy ich standardowe wyjście jest potokiem zamiast terminala.
cjm

3
@cjm - Prawda. Niektóre buforowanie wyjściowe można złagodzić za pomocą stdbuf -o 0, ale jeśli program ręcznie obsługuje buforowanie wyjściowe, nie pomoże (chyba że istnieje opcja wyłączenia / zmniejszenia wielkości bufora wyjściowego).
Chris Down,

2
W przypadku Pythona możesz wyłączyć buforowanie linii za pomocąpython -u
ibizaman

@Bwmat No. ... for x in sys.stdiniteruje po liniach bez buforowania ich wszystkich najpierw w pamięci.
Chris Down

Zrób to, a otrzymasz buforowanie ... za 1 1 1 1 1; śpij 1; Echo; zrobione | 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)))) '
ChuckCottrill

41

Do pomiaru delta linia po linii spróbuj gnomon .

Jest to narzędzie wiersza poleceń, trochę jak ts moreutils, do dodawania informacji o znaczniku czasu do standardowego wyjścia innego polecenia. Przydatny w długotrwałych procesach, w których potrzebujesz historycznego zapisu tego, co trwa tak długo.

Przesłanie czegokolwiek do gnomona spowoduje wstawienie znacznika czasu do każdej linii, wskazując, jak długo ta linia była ostatnią linią w buforze - to znaczy, ile czasu zajęło wyświetlenie 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 gnomon


Wygląda na doskonałą alternatywę do tskorzystania z procesów na żywo. Chociaż tslepiej nadaje się do procesów nieinteraktywnych.
BrainStone

7

Post Ryana zawiera ciekawy pomysł, jednak pod wieloma względami zawodzi. Podczas testowania tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 zauważyłem, że znacznik czasu pozostaje taki sam, nawet jeśli stdoutpóźniej, z różnicami 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 odpowiednie oznaczanie czasu i wykorzystuje nieco bardziej przenośne printfniż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 -copcji pierwszy argument zostaje przypisany $0i nie pojawia się w danych wyjściowych. Zapoznaj się ze stroną podręcznika powłoki, aby uzyskać właściwy opis-c

Testowanie to rozwiązanie tail -f /var/log/syslogi (jak zapewne mógł odgadnąć) odłączeniu i ponownym podłączeniu do mojego wifi, wykazała właściwy czas tłoczenia zapewnia zarówno datei syslogkomunikaty

Bash można zastąpić dowolną powłoką podobną do bourne, można to zrobić za pomocą jednego kshlub dashprzynajmniej tych, które mają taką -copcję.

Potencjalne problemy:

Rozwiązanie wymaga posiadania xargs, który jest dostępny w systemach zgodnych z POSIX, więc większość systemów uniksowych powinna zostać objęta. Oczywiście nie będzie działać, jeśli twój system nie jest zgodny z POSIX lub go nie maGNU findutils


5

Wolałbym komentować powyżej, ale nie mogę, reputacyjnie. W każdym razie powyższą próbkę Perla można niebuforować 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'

Pierwsze „$ |” rozpakowuje STDOUT. Drugi ustawia stderr jako bieżący domyślny kanał wyjściowy i odznacza go. Ponieważ select zwraca oryginalne ustawienie $ |, owijając select wewnątrz select, resetujemy również $ | do wartości domyślnej STDOUT.

I tak, możesz wycinać i wklejać jak jest. Wielowarstwowo podłożyłem go 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)'

1
Działa jak urok, bez konieczności instalowania niestandardowych pakietów.
Jay Taylor

2

Większość odpowiedzi sugeruje użycie date, ale jest wystarczająco powolna. Jeśli twoja wersja bash jest większa niż 4.2.0, lepiej użyć printfzamiast tego, jest to wbudowana bash. Jeśli potrzebujesz obsługi starszych wersji bash, możesz utworzyć logfunkcję zależną od wersji bash:

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 różnice prędkości:

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}")tego lepiej jest użyć $(exec date +"${TIMESTAMP_FORMAT}")lub nawet $(exec -c date +"${TIMESTAMP_FORMAT}")przyspieszyć wykonanie.


0

Możesz to zrobić za pomocą datei xargs:

... | xargs -L 1 echo `date +'[%Y-%m-%d %H:%M:%S]'` $1

Wyjaśnienie:

xargs -L 1mówi xargsowi, aby uruchomił polecenie kontynuujące dla każdego 1 wiersza danych wejściowych, i przekazuje to w pierwszym wierszu. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1w zasadzie powtarza datę z argumentem wejściowym na końcu


2
Rozwiązanie jest bliskie, ale nie sygnalizuje odpowiednio czasu, jeśli chodzi o dane wyjściowe oddzielone długimi okresami czasu. Używasz także backticksa i nie cytowałeś $1. To nie jest dobry styl. Zawsze podawaj zmienne. Ponadto korzystasz echo, co nie jest przenośne. Jest w porządku, ale może nie działać poprawnie w niektórych systemach.
Sergiy Kolodyazhnyy

Po przetestowaniu tego wydaje się, że masz całkowitą rację ... czy znasz jakiś sposób, aby dokonać dateponownej oceny każdej linii, czy jest to raczej beznadziejne?
Ryan,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.