Deseo añadir una marca de tiempo a cada línea de salida de un comando. Por ejemplo:
foo bar baz
se convertiría en
[2011-12-13 12:20:38] foo [2011-12-13 12:21:32] bar [2011-12-13 12:22:20] baz
… donde el tiempo es prefijado es la hora en que se imprimió la línea. ¿Cómo puedo lograr esto?
Comentarios
- ¿Existe una utilidad Unix para anteponer marcas de tiempo a stdin?
Responder
moreutils incluye ts
que hace esto muy bien:
command | ts "[%Y-%m-%d %H:%M:%S]"
Se 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
aña ¿Desea saber cuándo se reinició ese servidor? Simplemente ejecute ping | ts
, problema resuelto: D.
Comentarios
- ¿Cómo no me he enterado de esto? ?!?!?! Esto complementa la cola -f asombrosamente!
tail -f /tmp/script.results.txt | ts
- Si no ‘ no tengo el comando ts, ¿qué debo usar?
- Si ‘ no funciona, intente redirigir stderr a stdout, por ejemplo
ssh -v 127.0.0.1 2>&1 | ts
- Instale haciendo
sudo apt install moreutils
en Debian yyum install moreutils
en Fedora. - Creo que señalar el parámetro
-s
es útil. Ya que muestra el tiempo de ejecución del comando. Personalmente me gusta usarts
yts -s
al mismo tiempo. Se parece a esto:command | ts -s '(%H:%M:%.S)]' | ts '[%Y-%m-%d %H:%M:%S'
. Esto antepone las líneas de registro como esta:[2018-12-04 08:31:00 (00:26:28.267126)] Hai <3
Respuesta
En primer lugar, si espera que estas marcas de tiempo representen realmente un evento, tenga en cuenta que, dado que muchos programas realizan el almacenamiento en búfer de línea (algunos de forma más agresiva que otros), es importante pensar en esto tan cerca del momento en que la línea original lo haría. se han impreso en lugar de una marca de tiempo de una acción que se está llevando a cabo.
También puede verificar que su comando no tenga una función incorporada dedicada a hacer esto. Como ejemplo, ping -D
existe en algunas ping
versiones e imprime el tiempo desde la época de Unix antes de cada línea. Sin embargo, si su comando no contiene su propio método, Hay algunos métodos y herramientas que se pueden emplear, entre otros:
shell POSIX
Tenga en cuenta que dado que muchos shells almacenan sus cadenas internamente como cstrings, si la entrada contiene el valor nulo carbonizarse acter (\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"
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] ")"
Comentarios
- Un problema aquí es que muchos programas se encienden incluso más búfer de salida cuando su salida estándar es una tubería en lugar de la terminal.
- @cjm – Verdadero. Se puede aliviar parte del almacenamiento en búfer de salida usando
stdbuf -o 0
, pero si el programa maneja manualmente su almacenamiento en búfer de salida, no ‘ ayuda (a menos que hay una opción para deshabilitar / reducir el tamaño del búfer de salida). - Para Python, puede deshabilitar el búfer de línea con
python -u
- @Bwmat No.
... for x in sys.stdin
itera sobre las líneas sin almacenarlas en búfer primero en la memoria. - Haga esto y obtendrá el almacenamiento en búfer … para a en 1 1 1 1 1; dormir 1; eco; done | python -c ‘ importar sys, time; sys.stdout.write (» » .join ((» » .join ((time.strftime (» [% Y-% m-% d% H:% M:% S] «, time.gmtime ()), line)) para la línea en sys.stdin))) ‘
Respuesta
Para una medición delta línea por línea , pruebe gnomon .
Es una utilidad de línea de comandos, un poco como moreutils «s ts, para anteponer la información de la marca de tiempo a la salida estándar de otro comando. Útil para procesos de larga ejecución en los que le gustaría tener un registro histórico de lo que está tardando tanto.
La canalización de cualquier cosa a gnomon anteponer una marca de tiempo a cada línea, indicando cuánto tiempo fue esa línea la última línea en el búfer, es decir, cuánto tiempo tardó en aparecer la siguiente línea. De forma predeterminada, gnomon mostrará los segundos transcurridos entre cada línea, pero eso es configurable.
Comentarios
- Parece una gran alternativa a
ts
cuando se utilizan procesos en vivo. Mientras quets
es más adecuado para procesos no interactivos.
Respuesta
Hubiera preferido comentar más arriba, pero no puedo, según mi reputación. De todos modos, la muestra de Perl anterior se puede eliminar del búfer 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 El primer «$ |» anula el búfer STDOUT. El segundo establece stderr como el canal de salida predeterminado actual y lo anula. Dado que select devuelve la configuración original de $ |, al envolver la selección dentro de una selección, también restablecemos $ | a su valor predeterminado, STDOUT.
Y sí, puedes cortar «n pegar como está. Lo alineé en varias líneas para que sea legible.
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)"
Comentarios
- Funciona a la perfección, sin tener que instalar ningún paquete no estándar.
Responder
Publicación de Ryan proporciona una idea interesante, sin embargo, falla en varios aspectos. Mientras probaba con tail -f /var/log/syslog | xargs -L 1 echo $(date +"[%Y-%m-%d %H:%M:%S]") $1
, noté que la marca de tiempo permanece igual incluso si stdout
viene más tarde con una diferencia en 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 un sello de tiempo adecuado y usa algo más portátil printf
en lugar de echo
| xargs -L 1 bash -c "printf "[%s] %s\n" "$(date +%Y-%m-%d\ %H:%M:%S )" "$*" " bash
Por qué bash -c "..." bash
? Porque debido a la opción -c
, primer argumento nt se asigna a $0
y no aparecerá en la salida. Consulte la página del manual de su shell para obtener la descripción adecuada de -c
Probando esta solución con tail -f /var/log/syslog
y (como probablemente puedas adivinar) desconectar y volver a conectar a mi wifi, ha mostrado la marca de tiempo adecuada proporcionada por los mensajes date
y syslog
Bash podría ser reemplazado por cualquier caparazón similar a bourne, podría hacerse con ksh
o dash
, al menos aquellos que tienen la opción -c
.
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 similares a Unix deberían estar cubiertos. Obviamente, no funcionará si su sistema no es compatible con POSIX o no tiene GNU findutils
Respuesta
La mayoría de las respuestas sugieren usar date
, pero es lo suficientemente lento . Si su versión de bash es mayor que 4.2.0, es mejor usar printf
en su lugar, es una versión de bash incorporada. Si necesita admitir versiones de bash heredadas, puede crear log
La función 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 velocidad diferencias:
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}")
es mejor utilizar $(exec date +"${TIMESTAMP_FORMAT}")
o incluso $(exec -c date +"${TIMESTAMP_FORMAT}")
acelerar la ejecución.
UPD2: bash 5 proporciona la variable EPOCHREALTIME
durante microsegundos granularidad, puede usarlo con este comando (aproximadamente un 30% más lento que solo segundos): printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Comentarios
- ¡Esta respuesta está muy subestimada! Particularmente agradable para mostrar el impacto relativo en el rendimiento.
- Excelente sugerencia, pero desafortunadamente la fecha no proporciona una resolución inferior a un segundo, que es un requisito en mi caso
- Con bash 5.0+ puede utilizar este comando:
printf "%(${TIMESTAMP_FORMAT})T.%s %5d %s\n" ${EPOCHREALTIME/./ } $$ "$*"
Responder
Puede hacer esto con date
y xargs
:
... | xargs -L 1 echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
Explicación:
xargs -L 1
le dice a xargs que ejecute el comando de procedimiento para cada 1 línea de entrada, y pasa en la primera línea mientras lo hace. echo `date +"[%Y-%m-%d %H:%M:%S]"` $1
básicamente repite la fecha con el argumento de entrada al final
Comentarios
- La solución está cerca, pero no ‘ t marca de tiempo correctamente cuando se trata de resultados separados por largos períodos de tiempo. Además, ‘ estás usando comillas invertidas y no has ‘ citado
$1
. Ese ‘ no es de buen estilo. Cita siempre las variables. Además, ‘ estás usandoecho
, que no es portátil. Está ‘ bien, pero puede que no funcione correctamente en algunos sistemas. - Después de probar esto, parece que ‘ tiene toda la razón … ¿Conoce alguna forma de hacer
date
reevaluar cada línea, ¿o es prácticamente inútil?
Responder
Enchufe descarado para algo que acaba de escribir para resolver este problema exacto: ets
, escrito en Go.
Puede encontrar muchos ejemplos de uso en la página del proyecto.
La diferencia notable con las respuestas existentes y ofertas similares es que ets
está diseñado para ejecutar su comando por usted, en un pty (pseudo tty), es decir, simulando su comando se ejecuta de forma nativa en un tty. En comparación con la salida del comando de canalización en, por ejemplo, ts
, esto hace que la marca de tiempo sea casi transparente y resuelve un montón de problemas de canalización:
- Algunos programas almacenan en búfer agresivamente al escribir en una canalización, por lo que no ver salida y luego un montón de salida (sí, puede stdbuf, incluso puede envolver stdbuf y ts en un alias / función, pero no sería mejor si las cosas funcionen fuera de la caja);
- Algunos programas deshabilitan el color y / o la interactividad cuando escriben en una tubería;
- El estado de salida desaparece a menos que haya activado pipefail; etc.
Los comandos pueden ser ejecutado directamente, lo que significa que simplemente puede anteponer ets
a su línea de comando existente, o pueden ser comandos de shell (como se muestra en el gif anterior). Por supuesto, si desea canalizar la salida, ets
también puede hacerlo.
ets
admite lo mismo Modos de marca de tiempo como moreutils ts
: modo de tiempo absoluto, modo de tiempo transcurrido y modo de tiempo incremental. Utiliza valores predeterminados más sensatos (por ejemplo, el reloj monótono siempre se usa para marcas de tiempo transcurridas / incrementales) y tiene soporte adicional para zonas horarias personalizadas. Hay una comparación detallada aquí .
De nuevo, https://github.com/zmwangx/ets . Pruébelo, informe errores, etc.
Respuesta
Pon esto cerca del comienzo de su secuencia de comandos
# prepend a timestamp to all output exec &> >( ts "%Y-%m-%d.%H%M.%.S " )
O, para obtener crédito adicional, envíe la salida al archivo de registro a través de:
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} )
para enviar tanto a la consola como al archivo de registro:
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} )
Las principales ventajas de hacer esto son separar el registro de todo lo demás, no saturando el cuerpo del script canalizando a tee
o similar en cada comando, y sin tener que escribir funciones de registro personalizadas y apegándose a la vieja echo
.
El programa ts
se encuentra en el paquete moreutils
, que debería estar disponible en cualquier sano régimen de administración. 🙂