Esiste un'utilità Unix per anteporre i timestamp a stdin?


168

Ho finito per scrivere una breve sceneggiatura per questo in Python, ma mi chiedevo se esistesse un'utilità in cui poter inserire del testo che anteponesse ogni riga con del testo - nel mio caso specifico, un timestamp. Idealmente, l'uso sarebbe qualcosa del tipo:

cat somefile.txt | prepend-timestamp

(Prima di rispondere a sed, ho provato questo:

cat somefile.txt | sed "s/^/`date`/"

Ma questo valuta il comando date solo una volta quando sed viene eseguito, quindi lo stesso timestamp è erroneamente anteposto a ciascuna riga.)


È cat somefile.txtun po '"fuorviante"? Mi aspetto che accada "subito" e che abbia un unico timestamp. Non sarebbe questo un miglior programma di test: (echo a; sleep 1; echo b; sleep 3; echo c; sleep 2)?
Ciro Santilli 13 冠状 病 六四 事件 法轮功

Risposte:


185

Potrei provare a usare awk:

<command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; fflush(); }'

Potrebbe essere necessario accertarsi che <command>produca un output con buffer di linea, ovvero svuota il flusso di output dopo ogni riga; il timestamp awkaggiunge sarà il tempo in cui la fine della riga è apparsa sulla relativa pipe di input.

Se awk mostra errori, prova gawkinvece.


31
Sul mio sistema "awk" stesso era il buffering. (Questo può essere problematico per i file di registro). Ho risolto usando: awk '{print strftime ("% Y-% m-% dT% H:% M:% S"), $ 0; fflush (); } "
user47741

19
strftime () sembra essere un'estensione GNU awk, quindi se sei su Mac OS, ad esempio, usa gawk invece di awk.
Joe Shaw,

Non smette di stupirmi di quanto sia potente la bash. Non pensavo che ci sarebbe stata una soluzione così semplice per questo compito complicato.
Ripristina l'

2
Per gli utenti di OS X, è necessario installarlo gawke utilizzarlo anziché awk. Se hai MacPorts:sudo port install gawk
Matt Williamson il

5
@teh_senaus Per quanto ne so, awk's strftime()non ha la precisione millisecondo. Tuttavia è possibile utilizzare il datecomando. Fondamentalmente basta tagliare i nanosecondi a tre caratteri. Si sarà simile a questa: COMMAND | while read -r line; do echo "$(date '+%Y-%m-%d %T.%3N') $line"; done. Nota che puoi abbreviare% H:% M:% S con% T. Se vuoi ancora usare awkper qualche motivo, puoi fare quanto segue:COMMAND | awk '{ "date +%Y-%m-%d\\ %T.%3N" | getline timestamp; print timestamp, $0; fflush(); }'
Sei

190

tsda moreutils anteporrà un timestamp a ogni riga di input che gli dai. Puoi formattarlo anche usando strftime.

$ echo 'foo bar baz' | ts
Mar 21 18:07:28 foo bar baz
$ echo 'blah blah blah' | ts '%F %T'
2012-03-21 18:07:30 blah blah blah
$ 

Per installarlo:

sudo apt-get install moreutils

2
per catturare e timbrare stderr allo stesso tempo: bad command 2>&1 | tsrisultati inJan 29 19:58:31 -bash: bad: command not found
rymo

Voglio usare il formato dell'ora 2014 Mar 21 18:07:28. Come lo ottengo?
becko,

@becko - secondo la pagina del manuale , fornire una strftimestringa di formato come argomento :, [.. command ..] | ts '%Y %b %d %T'ad esempio.
Toby Speight,

2
Per OS X, brew install moreutils. Per emettere UTC, è possibile impostare TZ localmente, ad es.ls -l |TZ=UTC ts '%Y-%m-%dT%H:%M:%SZ'
karmakaze

1
@Dorian questo perché ruby ​​esegue il buffering dell'output; se si scarica il buffer prima di dormire, funziona abbastanza bene:ruby -e "puts 1; STDOUT.flush; sleep 1; puts 2; STDOUT.flush; sleep 2; puts 3" | ts '%F %T'
umläute,

45

annotate , disponibile tramite quel link o come annotate-outputnel devscriptspacchetto Debian .

$ echo -e "a\nb\nc" > lines
$ annotate-output cat lines
17:00:47 I: Started cat lines
17:00:47 O: a
17:00:47 O: b
17:00:47 O: c
17:00:47 I: Finished with exitcode 0

1
Questa è un'ottima soluzione, ma non funziona con l'output convogliato o con l'output secondario. Riformatterà l'output solo per un programma o uno script che viene fornito come argomento.
slm,

1
annotate-output funziona bene ed è facile da usare, ma è DAVVERO lento.
Paul Brannan,

31

Distillare le risposte fornite alla più semplice possibile:

unbuffer $COMMAND | ts

Su Ubuntu, provengono dai pacchetti prevedono-dev e moreutils.

sudo apt-get install expect-dev moreutils

1
Non lo expectè expect-dev, ma il secondo attira il primo, quindi funziona. (Ubuntu 14.10, mi chiedo se il binario sia stato spostato in un pacchetto diverso ad un certo punto.)
Marius Gedminas,

Per Ubuntu 14.04 LTS, è ancora expect-dev
attivo

2
Se desideri ottenere il tempo trascorso con precisione in millisecondi, usa unbuffer $COMMAND | ts -s %.s( -sse per il tempo trascorso e %.sper il tempo in secondi con la parte frazionaria)
Greg Witczak,

24

Cosa ne pensi di questo?

cat somefile.txt | perl -pne 'print scalar(localtime()), " ";'

A giudicare dal tuo desiderio di ottenere timestamp dal vivo, forse vuoi fare l'aggiornamento dal vivo su un file di registro o qualcosa del genere? Può essere

tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps

4
Questo esempio di Perl è stato particolarmente utile per me poiché stavo lavorando come utente su una distribuzione di Ubuntu, che apparentemente usa 'mawk' invece di 'gawk' - e non ha la funzione strftime. Grazie!
opello,

4
+1 a questo utile su un'installazione predefinita di Ubuntu. Ero solito tail -f /path/to/log | perl -pne 'use POSIX qw(strftime); print strftime "%Y-%m-%dT%H:%M:%SZ ", gmtime();'ottenere una data in stile ISO8601 / RFC3339 invece di quella che la versione più semplice produce.
natevw,

1
Se si desidera poter visualizzare l'output del registro con timestamp man mano che arriva, può essere utile aggiungere BEGIN { $|++; }prima dell'istruzione print per fare in modo che Perl svuoti l'output con ogni riga.

2
Puoi abbreviare ulteriormente lo script perl a just ls | perl -pne 'print localtime." "'. La conversione scalare avviene a causa della concatenazione di stringhe.
rahul,

Perché usi entrambi -pe le -nopzioni? Sembra -nridondante se è stato specificato -p.
Davor Cubranic


18

La risposta di Kieron è la migliore finora. Se hai problemi perché il primo programma sta eseguendo il buffering, puoi usare il programma unbuffer:

unbuffer <command> | awk '{ print strftime("%Y-%m-%d %H:%M:%S"), $0; }'

È installato di default sulla maggior parte dei sistemi Linux. Se è necessario crearlo da soli, fa parte del pacchetto prevede

http://expect.nist.gov


Si noti che ho scoperto che "unbuffer" a volte nasconde il risultato di ritorno del programma chiamato - quindi non è utile in Jenkins o altre cose che dipendono dallo stato di ritorno
oskarpearson

10

Utilizzare il comando read (1) per leggere una riga alla volta dall'input standard, quindi emettere la riga anteposta con la data nel formato scelto usando date (1).

$ cat timestamp
#!/bin/sh
while read line
do
  echo `date` $line
done
$ cat somefile.txt | ./timestamp

2
Un po 'pesante in quanto prevede un nuovo processo per riga, ma funziona!
Joe Shaw,

3

Non sono un ragazzo Unix, ma penso che tu possa usare

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt

3
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

2

Ecco la mia soluzione awk (da un sistema Windows / XP con MKS Tools installato nella directory C: \ bin). È progettato per aggiungere la data e l'ora correnti nella forma mm / gg hh: mm all'inizio di ogni riga dopo aver recuperato il timestamp dal sistema mentre viene letta ogni riga. Naturalmente, è possibile utilizzare il modello BEGIN per recuperare il timestamp una volta e aggiungere quel timestamp a ciascun record (lo stesso). Ho fatto questo per taggare un file di registro che veniva generato su stdout con il timestamp al momento della generazione del messaggio di registro.

/"pattern"/ "C\:\\\\bin\\\\date '+%m/%d %R'" | getline timestamp;
print timestamp, $0;

dove "pattern" è una stringa o regex (senza virgolette) da abbinare nella riga di input ed è facoltativo se si desidera abbinare tutte le righe di input.

Questo dovrebbe funzionare anche su sistemi Linux / UNIX, basta eliminare C \: \\ bin \\ lasciando la linea

             "date '+%m/%d %R'" | getline timestamp;

Questo, naturalmente, presuppone che il comando "date" porti al comando standard display / set di date Linux / UNIX senza specifiche informazioni sul percorso (ovvero, la variabile PATH dell'ambiente sia configurata correttamente).


Su OS X avevo bisogno di uno spazio prima del comando date. Purtroppo non sembra rivalutare il comando per ogni riga. Come altri hanno suggerito, sto cercando di aggiungere timestamp a tail-f.
Mark Bennett,

2

Mescolando alcune risposte sopra da Natevw e Frank Ch. Eigler.

Ha millisecondi, funziona meglio che chiamare dateogni volta un comando esterno e il perl può essere trovato nella maggior parte dei server.

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday);
  use POSIX qw(strftime);
  ($s,$ms) = gettimeofday();
  print strftime "%Y-%m-%dT%H:%M:%S+$ms ", gmtime($s);
  '

Versione alternativa con flush e read in loop:

tail -f log | perl -pne '
  use Time::HiRes (gettimeofday); use POSIX qw(strftime);
  $|=1;
  while(<>) {
    ($s,$ms) = gettimeofday();
    print strftime "%Y-%m-%dT%H:%M:%S+$ms $_", gmtime($s);
  }'

1
Due difetti con quanto sopra: 1. $ ms non è allineato e con zero. 2. Eventuali codici% nel registro verranno modificati. Quindi, invece della linea di stampa, ho usato: printf "%s+%06d %s", (strftime "%Y-%m-%dT%H:%M:%S", gmtime($s)), $ms, $_;
Simon Pickup

2
$ cat somefile.txt | sed "s/^/`date`/"

puoi farlo (con gnu / sed ):

$ some-command | sed "x;s/.*/date +%T/e;G;s/\n/ /g"

esempio:

$ { echo 'line1'; sleep 2; echo 'line2'; } | sed "x;s/.*/date +%T/e;G;s/\n/ /g"
20:24:22 line1
20:24:24 line2

ovviamente, puoi usare altre opzioni della data del programma . sostituiscilo date +%Tcon quello che ti serve.


1

La risposta di caerwyn può essere eseguita come una subroutine, che impedirebbe i nuovi processi per riga:

timestamp(){
   while read line
      do
         echo `date` $line
      done
}

echo testing 123 |timestamp

3
in che modo ciò impedisce datedi essere generato su ogni linea?
Gyom,

@Gyom Non impedisce la generazione di ogni riga. Impedisce alla shell di generare ogni linea. Vorrei aggiungere che in bash potresti fare qualcosa del genere: timestamp() { while read line; do echo "$(date) $line"; done; }; export -f timestampin una sceneggiatura che fonte.
smbear,

Chiamare la data come sottoprocesso significa comunque generarla per ogni linea, tuttavia si avvia lo script in primo luogo.
Peter Hansen,

3
Per impedire il comando di spawn dateper ogni riga, prova ad usare bash printfbuiltin con il formato T% (datepec) . Quindi potrebbe sembrare timestamp() { while IFS= read -r line; do printf "%(%F %T)T %s\n" -1 "$line"; done; }. I builtin della shell non verranno generati. Formato Datespec è come strftime(3)per esempio datei formati. Ciò richiede bash, non sono sicuro da quale versione supporti tale printfidentificatore.
Mindaugas Kubilius,

1

Disclaimer : la soluzione che sto proponendo non è un'utilità integrata Unix.

Ho affrontato un problema simile qualche giorno fa. Non mi piacevano la sintassi e i limiti delle soluzioni sopra, quindi ho rapidamente messo insieme un programma in Go per fare il lavoro per me.

Puoi controllare lo strumento qui: preftime

Esistono eseguibili predefiniti per Linux, MacOS e Windows nella sezione Rilasci del progetto GitHub.

Lo strumento gestisce linee di output incomplete e ha (dal mio punto di vista) una sintassi più compatta.

<command> | preftime

Non è l'ideale, ma ho pensato di condividerlo nel caso in cui aiuti qualcuno.


0

farlo con datee tre xargssu OSX:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S\" | tr \"\n\" \" \"; echo \"{}\"'"
<command> | predate

se vuoi millisecondi:

alias predate="xargs -I{} sh -c 'date +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

ma nota che su OSX, date non ti dà l'opzione% N, quindi dovrai installare gdate ( brew install coreutils) e quindi finalmente arrivare a questo:

alias predate="xargs -I{} sh -c 'gdate +\"%Y-%m-%d %H:%M:%S.%3N\" | tr \"\n\" \" \"; echo \"{}\"'"

-1

Se il valore che stai anteponendo è lo stesso su ogni riga, avvia emacs con il file, quindi:

Ctrl + <space>

all'inizio del file (per contrassegnare quel punto), quindi scorrere verso il basso fino all'inizio dell'ultima riga (Alt +> andrà alla fine del file ... che probabilmente coinvolgerà anche il tasto Maiusc, quindi Ctrl + a per andare all'inizio di quella riga) e:

Ctrl + x r t

Qual è il comando da inserire nel rettangolo che hai appena specificato (un rettangolo di larghezza 0).

2008-8-21 18:45 <invio>

O qualunque cosa tu voglia anteporre ... vedrai quel testo anteposto ad ogni riga all'interno del rettangolo di larghezza 0.

AGGIORNAMENTO: Ho appena capito che non vuoi la stessa data, quindi questo non funzionerà ... anche se potresti essere in grado di farlo in emacs con una macro personalizzata leggermente più complicata, ma comunque, questo tipo di modifica del rettangolo è molto carino da sapere su ...

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.