¿Existe una utilidad Unix para anteponer marcas de tiempo a stdin?

168

Terminé escribiendo una pequeña secuencia de comandos rápida para esto en Python, pero me preguntaba si había una utilidad en la que pudiera alimentar el texto que antepondría cada línea con algo de texto, en mi caso específico, una marca de tiempo. Idealmente, el uso sería algo como:

cat somefile.txt | prepend-timestamp

(Antes de responder sed, probé esto:

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

Pero eso solo evalúa el comando de fecha una vez cuando se ejecuta sed, por lo que la misma marca de tiempo se antepone incorrectamente a cada línea).

Joe Shaw
fuente
¿Es cat somefile.txtun poco "engañoso"? Espero que suceda "a la vez" y tenga una sola marca de tiempo. ¿No sería este un mejor programa de prueba (echo a; sleep 1; echo b; sleep 3; echo c; sleep 2):?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

185

Podría intentar usar awk:

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

Es posible que deba asegurarse de que <command>produzca una salida con buffer de línea, es decir, enjuague su flujo de salida después de cada línea; la marca de tiempo que se awkagrega será el momento en que el final de la línea apareció en su canal de entrada.

Si awk muestra errores, intente en su gawklugar.

Kieron
fuente
31
En mi sistema, 'awk' en sí mismo estaba almacenando en búfer. (Esto puede ser problemático para los archivos de registro). Lo arreglé usando: awk '{print strftime ("% Y-% m-% dT% H:% M:% S"), $ 0; fflush (); } '
usuario47741
19
strftime () parece ser una extensión awk de GNU, por lo que si está en Mac OS, por ejemplo, use gawk en lugar de awk.
Joe Shaw
No deja de sorprenderme lo poderoso que es bash. No pensé que habría una solución tan fácil para esta complicada tarea.
recluze
2
Para los usuarios de OS X, debe instalarlo gawky usarlo en lugar de awk. Si tiene MacPorts:sudo port install gawk
Matt Williamson el
55
@teh_senaus Hasta donde yo sé, awk's strftime()no tiene precisión de milisegundos. Sin embargo, puedes usar el datecomando. Básicamente, solo recorta nanosegundos a tres caracteres. Se parece a esto: COMMAND | while read -r line; do echo "$(date '+%Y-%m-%d %T.%3N') $line"; done. Tenga en cuenta que puede abreviar% H:% M:% S con% T. Si aún desea usarlo awkpor algún motivo, puede hacer lo siguiente:COMMAND | awk '{ "date +%Y-%m-%d\\ %T.%3N" | getline timestamp; print timestamp, $0; fflush(); }'
Seis
190

tsfrom moreutils agregará una marca de tiempo a cada línea de entrada que le dé. También puede formatearlo 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
$ 

Para instalarlo:

sudo apt-get install moreutils
Mark McKinstry
fuente
2
capturar y estampar stderr al mismo tiempo: bad command 2>&1 | tsresultados enJan 29 19:58:31 -bash: bad: command not found
rymo
Quiero usar el formato de hora 2014 Mar 21 18:07:28. ¿Como lo consigo?
Becko
@becko: de acuerdo con la página del manual , proporcione una strftimecadena de formato como argumento: [.. command ..] | ts '%Y %b %d %T'por ejemplo.
Toby Speight
2
Para OS X, brew install moreutils. Para generar UTC, puede configurar TZ localmente, por ejemplols -l |TZ=UTC ts '%Y-%m-%dT%H:%M:%SZ'
karmakaze el
1
@Dorian esto se debe a que ruby ​​está almacenando la salida en el búfer; si vaciar el búfer antes de dormir funciona lo suficientemente bien:ruby -e "puts 1; STDOUT.flush; sleep 1; puts 2; STDOUT.flush; sleep 2; puts 3" | ts '%F %T'
Umlaute
45

anotar , disponible a través de ese enlace o como annotate-outputen el devscriptspaquete 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
Ted Percival
fuente
1
Esta es una gran solución, pero no funcionará con salida canalizada o salida subcapada. Solo formateará la salida para un programa o script que le dé como argumento.
slm
1
annotate-output funciona bien y es fácil de usar, pero es REALMENTE lento.
Paul Brannan
31

Destilando las respuestas dadas a la más simple posible:

unbuffer $COMMAND | ts

En Ubuntu, provienen de los paquetes expect-dev y moreutils.

sudo apt-get install expect-dev moreutils
Willem
fuente
1
No lo es expect, expect-devpero este último atrae al primero, así que esto funciona. (Ubuntu 14.10, me pregunto si el binario se movió a un paquete diferente en algún momento).
Marius Gedminas
Para Ubuntu 14.04 LTS, todavía está enexpect-dev
Willem
2
Si desea obtener el tiempo transcurrido con una precisión de milisegundos, use unbuffer $COMMAND | ts -s %.s( -ssi es por tiempo transcurrido y %.spor tiempo en segundos con parte fraccional)
Greg Witczak
24

¿Qué tal esto?

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

A juzgar por su deseo de obtener marcas de tiempo en vivo, ¿tal vez quiera hacer una actualización en vivo en un archivo de registro o algo así? Tal vez

tail -f /path/to/log | perl -pne 'print scalar(localtime()), " ";' > /path/to/log-with-timestamps
jj33
fuente
44
Este ejemplo de Perl fue particularmente útil para mí, ya que estaba trabajando como usuario en una implementación de ubuntu, que aparentemente usa 'mawk' en lugar de 'gawk', y no tiene la función strftime. ¡Gracias!
opello
44
+1 a esto es útil en una instalación predeterminada de Ubuntu. Solía tail -f /path/to/log | perl -pne 'use POSIX qw(strftime); print strftime "%Y-%m-%dT%H:%M:%SZ ", gmtime();'obtener una fecha de estilo ISO8601 / RFC3339 en lugar del wacko que produce la versión más simple.
natevw
1
Si desea poder ver la salida del registro con marca de tiempo a medida que llega, puede ser útil agregar BEGIN { $|++; }antes de la declaración de impresión para que Perl vacíe su salida con cada línea.
2
Puede acortar aún más el script perl a solo ls | perl -pne 'print localtime." "'. La conversión escalar ocurre debido a la concatenación de cadenas.
rahul
¿Por qué usas ambos -py las -nopciones? Parece que -nes redundante si lo ha especificado -p.
Davor Cubranic
19

Solo voy a lanzar esto: hay un par de utilidades en daemontools llamadas tai64n y tai64nlocal que están hechas para preimprimir marcas de tiempo para registrar mensajes.

Ejemplo:

cat file | tai64n | tai64nlocal
chazomaticus
fuente
18

La respuesta de Kieron es la mejor hasta ahora. Si tiene problemas porque el primer programa está almacenando el búfer, puede usar el programa unbuffer:

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

Se instala por defecto en la mayoría de los sistemas Linux. Si necesita construirlo usted mismo, es parte del paquete esperado

http://expect.nist.gov

Mark Harrison
fuente
Tenga en cuenta que he encontrado que 'unbuffer' a veces se esconde el resultado de retorno del programa que se llama - lo que no es útil en Jenkins u otras cosas que se basan en el estado de retorno
oskarpearson
10

Use el comando read (1) para leer una línea a la vez desde la entrada estándar, luego envíe la línea antepuesta con la fecha en el formato que elija usando date (1).

$ cat timestamp
#!/bin/sh
while read line
do
  echo `date` $line
done
$ cat somefile.txt | ./timestamp
caerwyn
fuente
2
Un poco pesado ya que bifurca un nuevo proceso por línea, ¡pero funciona!
Joe Shaw
3

No soy un tipo de Unix, pero creo que puedes usar

gawk '{print strftime("%d/%m/%y",systime()) $0 }' < somefile.txt
PabloG
fuente
3
#! /bin/sh
unbuffer "$@" | perl -e '
use Time::HiRes (gettimeofday);
while(<>) {
        ($s,$ms) = gettimeofday();
        print $s . "." . $ms . " " . $_;
}'

fuente
2

Aquí está mi solución awk (de un sistema Windows / XP con MKS Tools instalado en el directorio C: \ bin). Está diseñado para agregar la fecha y hora actuales en la forma mm / dd hh: mm al comienzo de cada línea que ha obtenido esa marca de tiempo del sistema a medida que se lee cada línea. Por supuesto, podría usar el patrón BEGIN para obtener la marca de tiempo una vez y agregar esa marca de tiempo a cada registro (de todos modos). Hice esto para etiquetar un archivo de registro que se estaba generando para stdout con la marca de tiempo en el momento en que se generó el mensaje de registro.

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

donde "patrón" es una cadena o expresión regular (sin las comillas) que debe coincidir en la línea de entrada, y es opcional si desea hacer coincidir todas las líneas de entrada.

Esto también debería funcionar en los sistemas Linux / UNIX, simplemente elimine el C \: \\ bin \\ que sale de la línea

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

Esto, por supuesto, supone que el comando "fecha" lo lleva al comando estándar de visualización / configuración de fecha de Linux / UNIX sin información de ruta específica (es decir, su variable de RUTA de entorno está configurada correctamente).

ElGringoGeek
fuente
En OS XI necesitaba un espacio antes del comando de fecha. Lamentablemente, no parece volver a evaluar el comando para cada línea. Como otros han sugerido, estoy tratando de agregar marcas de tiempo a la cola -f.
Mark Bennett
2

Mezclando algunas respuestas anteriores de natevw y Frank Ch. Eigler

Tiene milisegundos, funciona mejor que llamar a un datecomando externo cada vez y se puede encontrar perl en la mayoría de los servidores.

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

Versión alternativa con descarga y lectura en bucle:

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);
  }'
Keymon
fuente
1
Dos fallas con lo anterior: 1. $ ms no está alineado y rellenado con ceros. 2. Cualquier código de% en el registro será destrozado. Por lo tanto, en lugar de la línea de impresión, utilicé: printf "%s+%06d %s", (strftime "%Y-%m-%dT%H:%M:%S", gmtime($s)), $ms, $_;
Simon Pickup
2
$ cat somefile.txt | sed "s/^/`date`/"

puedes hacer esto (con gnu / sed ):

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

ejemplo:

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

por supuesto, puede usar otras opciones de la fecha del programa . simplemente reemplace date +%Tcon lo que necesita.

aleksandr barakin
fuente
1

La respuesta de caerwyn se puede ejecutar como una subrutina, lo que evitaría los nuevos procesos por línea:

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

echo testing 123 |timestamp
crumplecrap
fuente
3
¿Cómo evita que datese genere en cada línea?
Gyom
@Gyom No impide que la fecha se genere en cada línea. Evita que la concha genere cada línea. Agregaría que en bash podrías hacer algo como: timestamp() { while read line; do echo "$(date) $line"; done; }; export -f timestampen un script que obtengas.
smbear
Llamar a la fecha como un subproceso todavía significa generarlo para cada línea, sin embargo, inicia el script en primer lugar.
Peter Hansen
3
Para evitar el comando de desove datepara cada línea, intente utilizar bash printfincorporado con formato % (datepec) T. Entonces puede parecer timestamp() { while IFS= read -r line; do printf "%(%F %T)T %s\n" -1 "$line"; done; }. Las conchas incorporadas no se generarán. El formato Datespec es como, por strftime(3)ejemplo, dateformatos. Esto requiere bash, no estoy seguro desde qué versión admite ese printfespecificador.
Mindaugas Kubilius
1

Descargo de responsabilidad : la solución que propongo no es una utilidad incorporada de Unix.

Me enfrenté a un problema similar hace unos días. No me gustaba la sintaxis y las limitaciones de las soluciones anteriores, por lo que rápidamente elaboré un programa en Ir para hacer el trabajo por mí.

Puede consultar la herramienta aquí: preftime

Hay ejecutables prediseñados para Linux, MacOS y Windows en la prensa sección del proyecto GitHub.

La herramienta maneja líneas de salida incompletas y tiene (desde mi punto de vista) una sintaxis más compacta.

<command> | preftime

No es ideal, pero pensé que lo compartiría en caso de que ayude a alguien.

Momchil Atanasov
fuente
0

hacerlo con datey try xargsen OSX:

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

si quieres milisegundos:

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

pero tenga en cuenta que en OSX, date no le da la opción% N, por lo que deberá instalar gdate ( brew install coreutils) y finalmente llegar a esto:

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

Si el valor que está anteponiendo es el mismo en cada línea, inicie emacs con el archivo y luego:

Ctrl + <space>

al comienzo del archivo (para marcar ese punto), luego desplácese hacia abajo hasta el comienzo de la última línea (Alt +> irá al final del archivo ... que probablemente también incluirá la tecla Shift, luego Ctrl + a para ir al comienzo de esa línea) y:

Ctrl + x r t

Cuál es el comando para insertar en el rectángulo que acaba de especificar (un rectángulo de 0 de ancho).

2008-8-21 6:45 PM <enter>

O lo que quiera anteponer ... entonces verá ese texto antepuesto a cada línea dentro del rectángulo de ancho 0.

ACTUALIZACIÓN: Me acabo de dar cuenta de que no quieres la MISMA fecha, por lo que esto no funcionará ... aunque es posible que puedas hacerlo en emacs con una macro personalizada un poco más complicada, pero aún así, este tipo de edición de rectángulo es bastante bueno saber sobre ...

Mike Stone
fuente