Preparare un timestamp per ogni riga di output da un comando


Risposte:


274

moreutils include tsche lo fa abbastanza bene:

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

Elimina anche la necessità di un loop, ogni linea di output avrà un timestamp su di esso.

$ 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

Vuoi sapere quando è tornato indietro il server che hai riavviato? Esegui ping | ts, problema risolto: D.


8
Come non ho saputo di questo?!?!?! Questo integra sorprendentemente la coda! tail -f /tmp/script.results.txt | ts
Bruno Bronosky,

Che dire di Cygwin? C'è qualcosa di simile? Non sembra che i moreutils di Joey siano lì.
CrazyPenguin

3
se non ho il comando ts, cosa dovrei usare?
ekassis,

1
Se non funziona, prova a reindirizzare stderr su stdout, ad esssh -v 127.0.0.1 2>&1 | ts
Jchook il

3
Penso che -ssia utile sottolineare il parametro . In questo modo viene visualizzato il runtime del comando. Personalmente mi piace usare entrambi tse ts -sallo stesso tempo. Sembra qualcosa di simile a questo: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Questo antepone le linee di registro in questo modo:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone

100

In primo luogo, se ti aspetti che questi timestamp rappresentino effettivamente un evento, tieni presente che poiché molti programmi eseguono il buffering di linea (alcuni in modo più aggressivo di altri), è importante pensare a questo come vicino al tempo che la linea originale avrebbe stato stampato anziché un timestamp di un'azione in corso.

Puoi anche verificare che il tuo comando non abbia già una funzione integrata dedicata a questo. Ad esempio, ping -Desiste in alcune pingversioni e stampa il tempo trascorso dall'epoca Unix prima di ogni riga. Se il tuo comando non contiene il suo metodo, tuttavia, ci sono alcuni metodi e strumenti che possono essere impiegati, tra gli altri:

Shell POSIX

Tenere presente che poiché molte shell memorizzano le proprie stringhe internamente come stringhe, se l'input contiene il carattere null ( \0), la linea potrebbe terminare prematuramente.

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'

Pitone

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

Rubino

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

3
Un problema qui è che molti programmi attivano ancora più buffering di output quando il loro stdout è una pipe invece del terminale.
cjm,

3
@cjm - True. Alcuni buffer di output possono essere alleviati utilizzando stdbuf -o 0, ma se il programma gestisce manualmente il buffering di output, non sarà di aiuto (a meno che non sia presente un'opzione per disabilitare / ridurre la dimensione del buffer di output).
Chris Down,

2
Per python, puoi disabilitare il buffering di linea conpython -u
ibizaman

@Bwmat No. scorre le ... for x in sys.stdinrighe senza prima memorizzarle tutte nella memoria.
Chris Down,

Fallo e otterrai il buffering ... per a in 1 1 1 1 1; dormire 1; eco; fatto | python -c 'import sys, time; sys.stdout.write ("". join (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S] ", time.gmtime ()), line)) per line in sys.stdin))) '
ChuckCottrill

41

Per una misurazione delta riga per riga, prova gnomon .

È un'utilità della riga di comando, un po 'come ts di moreutils, per anteporre le informazioni di data e ora all'output standard di un altro comando. Utile per i processi di lunga durata in cui desideri un record storico di ciò che richiede così tanto tempo.

Il piping di qualsiasi cosa su gnomon antepone un timestamp a ciascuna riga, indicando per quanto tempo quella riga era l'ultima riga nel buffer, ovvero quanto tempo impiegava la riga successiva ad apparire. Per impostazione predefinita, gnomon visualizzerà i secondi trascorsi tra ciascuna riga, ma è configurabile.

demo di gnomon


Sembra un'ottima alternativa tsall'utilizzo di processi live. Mentre tsè più adatto per processi non interattivi.
BrainStone,

7

Il post di Ryan fornisce un'idea interessante, tuttavia fallisce sotto diversi aspetti. Durante i test con tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 , ho notato che il timestamp rimane lo stesso anche se stdoutarriva più tardi con la differenza tra i secondi. Considera questo output:

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

La mia soluzione proposta è simile, tuttavia fornisce un adeguato timestamp e utilizza un po 'più portatile printfpiuttosto cheecho

| xargs -L 1 bash  -c 'printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" ' bash

Perché bash -c '...' bash? Perché a causa -cdell'opzione, il primo argomento viene assegnato $0e non verrà visualizzato nell'output. Consultare la pagina del manuale della shell per la descrizione corretta di-c

Testare questa soluzione con tail -f /var/log/sysloge (come probabilmente intuiresti) disconnettersi e riconnettersi al mio wifi, ha mostrato il giusto timestamp fornito da entrambi datee dai syslogmessaggi

Bash potrebbe essere sostituito da qualsiasi shell bourne-like, potrebbe essere fatto con uno ksho dash, almeno, con l' -copzione.

Potenziali problemi:

La soluzione richiede di avere xargs, che è disponibile su sistemi conformi a POSIX, quindi la maggior parte dei sistemi simili a Unix dovrebbe essere coperta. Ovviamente non funzionerà se il tuo sistema non è conforme POSIX o noGNU findutils


5

Avrei preferito commentare sopra, ma non posso, reputazionalmente. Ad ogni modo, il campione Perl sopra può essere senza buffer come segue:

command | perl -pe 'use POSIX strftime; 
                    $|=1; 
                    select((select(STDERR), $| = 1)[0]);
                    print strftime "[%Y-%m-%d %H:%M:%S] ", localtime'

Il primo '$ |' unbuffers STDOUT. Il secondo imposta stderr come canale di output predefinito corrente e lo sblocca. Poiché select restituisce l'impostazione originale di $ |, avvolgendo la selezione all'interno di una selezione, ripristiniamo anche $ | al suo valore predefinito, STDOUT.

E sì, puoi tagliare e incollare così com'è. L'ho allineato per leggibilità.

E se vuoi davvero essere preciso (e hai installato 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
Funziona come un fascino, senza dover installare pacchetti non standard.
Jay Taylor,

2

La maggior parte delle risposte suggerisce di usare date, ma è abbastanza lenta. Se la tua versione bash è successiva alla 4.2.0 è meglio usarla printfinvece, è un built-in bash. Se devi supportare versioni legh bash puoi creare una logfunzione dipende dalla versione 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

Vedi le differenze di velocità:

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: invece di $(date +"${TIMESTAMP_FORMAT}")è meglio usare $(exec date +"${TIMESTAMP_FORMAT}")o anche $(exec -c date +"${TIMESTAMP_FORMAT}")un'esecuzione troppo veloce.


0

Puoi farlo con datee xargs:

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

Spiegazione:

xargs -L 1dice a xargs di eseguire il comando di avanzamento per ogni 1 riga di input, e passa nella prima riga mentre lo fa. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1fondamentalmente fa eco alla data con l'argomento input alla fine di esso


2
La soluzione è vicina, ma non timestamp correttamente quando si tratta di output separati da lunghi periodi di tempo. Inoltre, stai usando i backtick e non li hai citati $1. Non è un buon stile. Cita sempre le variabili. Inoltre, stai utilizzando echo, che non è portatile. Va bene, ma potrebbe non funzionare correttamente su alcuni sistemi.
Sergiy Kolodyazhnyy,

Dopo aver provato questo, sembra che tu abbia assolutamente ragione ... conosci qualche modo per far daterivalutare ogni riga o è praticamente senza speranza?
Ryan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.