Anteporre un timestamp a ciascuna riga di output di un comando

Desidero anteporre un timestamp a ciascuna riga di output di un comando. Ad esempio:

foo bar baz 

diventerebbe

[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz 

… dove è il momento prefisso è lora in cui la riga è stata stampata. Come posso ottenere questo risultato?

Commenti

Risposta

moreutils include ts che lo fa abbastanza bene:

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

It elimina anche la necessità di un ciclo, ogni riga di output avrà un timestamp inserito su di essa.

$ 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 il server che hai riavviato? Esegui ping | ts, problema risolto: D.

Commenti

  • Come ho fatto a non saperlo ?!?!?! Questo integra incredibilmente tail -f! tail -f /tmp/script.results.txt | ts
  • se ‘ non ho il comando ts, cosa dovrei usare?
  • Se ‘ non funziona, prova a reindirizzare stderr a stdout, ad es. ssh -v 127.0.0.1 2>&1 | ts
  • Installa eseguendo sudo apt install moreutils su Debian e yum install moreutils su Fedora.
  • Penso che sia utile indicare il parametro -s. Poiché viene visualizzato il runtime del comando. Personalmente, mi piace utilizzare contemporaneamente ts e ts -s. Ha un aspetto simile a questo: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Questo antepone le righe di log in questo modo: [2018-12-04 08:31:00 (00:26:28.267126)] Hai <3

Answer

In primo luogo, se ti aspetti che questi timestamp rappresentino effettivamente un evento, tieni presente che poiché molti programmi eseguono il buffer di riga (alcuni in modo più aggressivo di altri), è importante pensare a questo come vicino al tempo che la riga originale avrebbe sono stati stampati anziché un timestamp di unazione in corso.

Potresti anche voler controllare che il tuo comando non abbia già una funzione incorporata dedicata a questo scopo. Ad esempio, ping -D esiste in alcune ping versioni e stampa lora dallepoca Unix prima di ogni riga. Se il tuo comando non contiene un proprio metodo, tuttavia, ci sono alcuni metodi e strumenti che possono essere impiegati, tra gli altri:

Shell POSIX

Tieni presente che poiché molte shell memorizzano le loro stringhe internamente come cstrings, se linput contiene il valore null char acter (\0), potrebbe far terminare prematuramente la riga.

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] ")" 

Commenti

  • Un problema qui è che molti programmi si accendono ancora più buffer di output quando il loro stdout è una pipe invece del terminale.
  • @cjm – True. Alcuni buffering di output possono essere alleviati utilizzando stdbuf -o 0, ma se il programma gestisce manualmente il buffering di output, ‘ non aiuta (a meno che cè unopzione per disabilitare / ridurre la dimensione del buffer di output).
  • Per python, puoi disabilitare il buffer di riga con python -u
  • @Bwmat No. ... for x in sys.stdin itera su righe senza prima bufferizzarle tutte in memoria.
  • Fai questo e otterrai il buffering … per un in 1 1 1 1 1; dormi 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 la riga in sys.stdin))) ‘

Risposta

Per una misurazione delta riga per riga , prova gnomon .

È unutilità della riga di comando, un po come moreutils “s ts, per anteporre le informazioni di timestamp allo standard output di un altro comando. Utile per processi a esecuzione prolungata in cui” si desidera una registrazione storica di ciò che sta impiegando così tanto tempo.

Inviare qualsiasi cosa a gnomon lo farà antepone un timestamp a ciascuna riga, indicando per quanto tempo quella riga era lultima riga nel buffer, ovvero quanto tempo è occorso per la visualizzazione della riga successiva. Per impostazione predefinita, gnomon mostrerà i secondi trascorsi tra ogni riga, ma è configurabile.

demo di gnomon

Commenti

  • Sembra unottima alternativa a ts quando si utilizzano processi live. Mentre ts è più adatto per processi non interattivi.

Risposta

Avrei preferito commentare sopra ma non posso, reputazionalmente. Ad ogni modo, lesempio di Perl sopra può essere sbloccato come segue:

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

Il il primo “$ |” sblocca STDOUT. Il secondo imposta stderr come canale di output predefinito corrente e lo sblocca. Dato che select restituisce limpostazione originale di $ |, inserendo il select allinterno di un select, reimpostiamo anche $ | al suo valore predefinito, STDOUT.

E sì, puoi tagliare “e incollare così comè. Lho multi-line per leggibilità.

E se vuoi davvero essere preciso (e hai Time :: Hires installato):

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

Commenti

  • Funziona come un incantesimo, senza dover installare pacchetti non standard.

Risposta

Ryan “post di fornisce unidea interessante, tuttavia, non riesce sotto diversi aspetti. Durante il 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 stdout arriva più tardi con la differenza di 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 una corretta indicazione dellora e utilizza un po più portabile printf anziché echo

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

Perché bash -c "..." bash? Perché a causa dellopzione -c, primo argomento nt viene assegnato a $0 e non viene visualizzato nelloutput. Consulta la pagina del manuale della tua shell per la corretta descrizione di -c

Testare questa soluzione con tail -f /var/log/syslog e (come probabilmente potresti indovinare) la disconnessione e la riconnessione al mio wifi, ha mostrato la corretta indicazione dellora fornita da entrambi i messaggi date e syslog p>

Bash potrebbe essere sostituito da qualsiasi shell simile a Bourne, potrebbe essere fatto con ksh o dash, almeno quelli che hanno lopzione -c.

Potenziali problemi:

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

Risposta

La maggior parte delle risposte suggerisce di utilizzare date, ma è abbastanza lento . Se la tua versione di bash è maggiore della 4.2.0, è meglio usare printf invece, è un builtin di bash. Se devi supportare le versioni precedenti di bash, puoi creare la funzione log dipende dalla versione di 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 velocità differenze:

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}") velocizza troppo lesecuzione.

UPD2: bash 5 fornisce EPOCHREALTIME variabile per microsecondi granularità, puoi usarlo con questo comando (solo circa il 30% più lento dei secondi): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Commenti

  • Questa risposta è molto sottovalutata! Particolarmente piacevole per mostrare limpatto relativo sulle prestazioni.
  • Ottimo suggerimento, ma sfortunatamente la data non fornisce una risoluzione inferiore al secondo, che è un requisito nel mio caso
  • Con bash 5.0+ puoi usare questo comando: printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"

Risposta

Puoi farlo con date e xargs:

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

Spiegazione:

xargs -L 1 dice a xargs di eseguire il comando che procede per ogni 1 riga di input, e lo fa nella prima riga. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1 sostanzialmente fa eco alla data con largomento di input alla fine

Commenti

  • La soluzione è vicino, ma ‘ t timestamp correttamente quando si tratta di output separati da lunghi periodi di tempo. Inoltre, ‘ stai utilizzando i backtick e non ‘ citato $1. Questo ‘ non è un buon stile. Cita sempre le variabili. Inoltre, ‘ stai utilizzando echo, che non è portabile. ‘ va bene, ma potrebbe non funzionare correttamente su alcuni sistemi.
  • Dopo averlo testato, sembra che ‘ abbia assolutamente ragione … conosci un modo per fare date viene rivalutato ogni riga o è praticamente senza speranza?

Answer

Spudorata spina per qualcosa che io ha appena scritto per risolvere questo problema esatto: ets , scritto in Go.

demo

Puoi trovare molti esempi di utilizzo nella pagina del progetto.

La notevole differenza rispetto alle risposte esistenti e offerte simili è che ets è progettato per eseguire il tuo comando per te, in una pty (pseudo tty), vale a dire simulando il tuo comando viene eseguito in modo nativo in una tty. Rispetto alloutput del comando piping in es. ts, questo rende il timestamp per lo più trasparente e risolve una serie di problemi di piping:

  • Alcuni programmi bufferizzano in modo aggressivo quando si scrive su un pipe, quindi non vedere alcun output e quindi un intero gruppo di output (sì, puoi eseguirne lo stdbuf, puoi persino racchiudere stdbuf e ts in un alias / funzione, ma non sarebbe meglio se le cose funzionassero fuori dagli schemi);
  • Alcuni programmi disabilitano il colore e / o linterattività durante la scrittura su una pipe;
  • Lo stato di uscita è scomparso a meno che tu non abbia attivato pipefail; ecc.

I comandi possono essere eseguito direttamente “ed, il che significa che puoi semplicemente anteporre ets alla tua riga di comando esistente, oppure possono essere comandi di shell (come mostrato nella gif sopra). Ovviamente, se vuoi reindirizzare loutput, ets può farlo anche tu.

ets supporta lo stesso modalità timestamp come moreutils ts: modalità tempo assoluto, modalità tempo trascorso e modalità tempo incrementale. Utilizza impostazioni predefinite più corrette (ad es. Lorologio monotono viene sempre utilizzato per i timestamp trascorsi / incrementali) e ha un supporto aggiuntivo per i fusi orari personalizzati. Cè “un confronto dettagliato qui .

Di nuovo, https://github.com/zmwangx/ets . Fai un giro, segnala bug, ecc.

Risposta

Metti questo vicino allinizio del tuo script

# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " ) 

Oppure, per un credito extra, invia loutput al file di log tramite:

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

per loutput sia nella console che nel file di log:

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

I principali vantaggi di questa operazione sono separare la registrazione da tutto il resto, non ingombrare il corpo dello script collegandolo a tee o simile su ogni comando, senza dover scrivere funzioni di registrazione personalizzate e attenersi al semplice echo.

Il programma ts si trova nel pacchetto moreutils, che dovrebbe essere prontamente disponibile in qualsiasi ragione sano regime di amministrazione. 🙂

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *