Anteponer una marca de tiempo a cada línea de salida de un comando

Respuestas:

274

moreutils incluye lo tsque hace esto bastante bien:

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

También elimina la necesidad de un bucle, cada línea de salida tendrá una marca de tiempo.

$ 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

¿Quiere saber cuándo volvió a funcionar ese servidor que reinició? Solo corre ping | ts, problema resuelto: D.

Mark McKinstry
fuente
8
¿Cómo no he sabido sobre esto?!?!?! ¡Esto complementa la cola -f asombrosamente! tail -f /tmp/script.results.txt | ts
Bruno Bronosky
¿Qué pasa en Cygwin? ¿Hay algo similar? No parece que las más utilidades de Joey estén ahí.
CrazyPenguin
3
si no tengo el comando ts, ¿qué debo usar?
ekassis
1
Si no funciona, intente redirigir stderr a stdout, por ejemplossh -v 127.0.0.1 2>&1 | ts
jchook
3
Creo que señalar el parámetro -ses útil. Como eso muestra el tiempo de ejecución del comando. Personalmente me gusta usar ambos tsy ts -sal mismo tiempo. Se ve algo como esto: command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'. Esto antepone las líneas de registro de esta manera:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
BrainStone
100

En primer lugar, si espera que estas marcas de tiempo realmente representen un evento, tenga en cuenta que dado que muchos programas realizan el almacenamiento en línea (algunos de forma más agresiva que otros), es importante pensar en esto como cerca del tiempo que tendría la línea original impreso en lugar de una marca de tiempo de una acción que tiene lugar.

También puede verificar que su comando no tenga una función incorporada dedicada a hacer esto. Como ejemplo, ping -Dexiste en algunas pingversiones e imprime el tiempo transcurrido desde la época de Unix antes de cada línea. Sin embargo, si su comando no contiene su propio método, existen algunos métodos y herramientas que pueden emplearse, entre otros:

Shell POSIX

Tenga en cuenta que, dado que muchos shells almacenan sus cadenas internamente como cstrings, si la entrada contiene el carácter nulo ( \0), puede hacer que la línea finalice 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'

Pitón

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

Rubí

command | ruby -pe 'print Time.now.strftime("[%Y-%m-%d %H:%M:%S] ")'
Chris Down
fuente
3
Un problema aquí es que muchos programas activan aún más el búfer de salida cuando su stdout es una tubería en lugar del terminal.
cjm
3
@cjm: cierto. Se puede aliviar parte del almacenamiento en búfer de salida mediante el uso stdbuf -o 0, pero si el programa maneja manualmente su almacenamiento en búfer de salida, no ayudará (a menos que haya una opción para deshabilitar / reducir el tamaño del búfer de salida).
Chris Down
2
Para python, puede deshabilitar el almacenamiento en línea conpython -u
ibizaman
@Bwmat No. ... for x in sys.stdinitera sobre las líneas sin almacenarlas primero en la memoria intermedia.
Chris Down
Haga esto y obtendrá almacenamiento en búfer ... para a en 1 1 1 1 1; duerme 1; eco; hecho | python -c 'import sys, time; sys.stdout.write ("". join (("" .join ((time.strftime ("[% Y-% m-% d% H:% M:% S] ", time.gmtime ()), line)) para línea en sys.stdin))) '
ChuckCottrill
41

Para una medición delta línea por línea, pruebe gnomon .

Es una utilidad de línea de comandos, un poco como ts de moreutils, para anteponer información de marca de tiempo a la salida estándar de otro comando. Útil para procesos de larga duración en los que desea un registro histórico de lo que lleva tanto tiempo.

Colocar cualquier cosa en gnomon antepondrá una marca de tiempo a cada línea, indicando cuánto tiempo fue esa última línea en el búfer, es decir, cuánto tiempo tardó en aparecer la siguiente. Por defecto, gnomon mostrará los segundos transcurridos entre cada línea, pero eso es configurable.

demo de gnomon

Janus Troelsen
fuente
Parece una gran alternativa a tscuando se utilizan procesos en vivo. Mientras que tses más adecuado para procesos no interactivos.
BrainStone
7

La publicación de Ryan proporciona una idea interesante, sin embargo, falla en varios aspectos. Mientras probaba tail -f /var/log/syslog | xargs -L 1 echo $(date +'[%Y-%m-%d %H:%M:%S]') $1 , noté que la marca de tiempo se mantiene igual, incluso si stdoutllega más tarde con una diferencia de segundos. Considere esta salida:

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

Mi solución propuesta es similar, sin embargo, proporciona una marca de tiempo adecuada y utiliza algo más portátil en printflugar deecho

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

¿Por qué bash -c '...' bash? Debido a la -copción, el primer argumento se asigna $0y no aparecerá en la salida. Consulte la página del manual de su shell para obtener la descripción adecuada de-c

Probar esta solución con tail -f /var/log/syslogy (como probablemente podría adivinar) desconectarse y volver a conectarse a mi wifi, ha mostrado la marca de tiempo adecuada proporcionada por ambos datey los syslogmensajes

Bash podría ser reemplazado por cualquier caparazón similar a bourne, podría hacerse con cualquiera ksho dash, al menos, aquellos que tienen -copción.

Problemas potenciales:

La solución requiere tener xargs, que está disponible en sistemas compatibles con POSIX, por lo que la mayoría de los sistemas tipo Unix deberían estar cubiertos. Obviamente no funcionará si su sistema no es compatible con POSIX o no tieneGNU findutils

Sergiy Kolodyazhnyy
fuente
5

Hubiera preferido comentar más arriba, pero no puedo, reputacionalmente. De todos modos, la muestra Perl anterior se puede quitar de la siguiente manera:

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

El primer '$ |' desabrocha STDOUT. El segundo establece stderr como el canal de salida predeterminado actual y lo desactiva. Dado que select devuelve la configuración original de $ |, al ajustar el select dentro de un select, también restablecemos $ | por defecto, STDOUT.

Y sí, puedes cortar y pegar como está. Lo forcé en varias líneas para mayor legibilidad.

Y si realmente quieres ser preciso (y tienes Time :: Hires instalado):

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)'
mpersico
fuente
1
Funciona a la perfección, sin tener que instalar ningún paquete no estándar.
Jay Taylor
2

La mayoría de las respuestas sugieren usar date, pero es lo suficientemente lento. Si su versión bash es superior a 4.2.0, es mejor usarla printf, es un bash incorporado. Si necesita admitir versiones de bash heredadas, puede crear una logfunción que depende de la versión de 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

Ver diferencias de velocidad:

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: en lugar de $(date +"${TIMESTAMP_FORMAT}")hacerlo, es mejor usarlo $(exec date +"${TIMESTAMP_FORMAT}")o incluso $(exec -c date +"${TIMESTAMP_FORMAT}")acelerar la ejecución.

Mikhail
fuente
0

Puedes hacer esto con datey xargs:

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

Explicación:

xargs -L 1le dice a xargs que ejecute el comando en curso para cada 1 línea de entrada, y pasa en la primera línea cuando lo hace. echo `date +'[%Y-%m-%d %H:%M:%S]'` $1básicamente hace eco de la fecha con el argumento de entrada al final

Ryan
fuente
2
La solución está cerca, pero no marca la hora correctamente cuando se trata de resultados separados por largos períodos de tiempo. Además, está utilizando backticks y no ha citado $1. Eso no es buen estilo. Siempre cite las variables. Además, está utilizando echo, que no es portátil. Está bien, pero puede no funcionar correctamente en algunos sistemas.
Sergiy Kolodyazhnyy
Después de probar esto, parece que tienes toda la razón ... ¿conoces alguna forma de datereevaluar cada línea, o es casi imposible?
Ryan