¿Cómo reemplazar las marcas de tiempo de época en un archivo con otros formatos?

10

Tengo un archivo que contiene fechas de época que necesito convertir a legible para humanos. Ya sé cómo hacer la conversión de fecha, por ejemplo:

[server01 ~]$ date -d@1472200700
Fri 26 Aug 09:38:20 BST 2016

.. pero estoy luchando por descubrir cómo sedrecorrer el archivo y convertir todas las entradas. El formato del archivo se ve así:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web
maquinista
fuente
1
Para referencia futura (suponiendo que este sea un archivo de historial de Bash; se parece a uno), busque la HISTTIMEFORMATvariable de shell para controlar el formato al momento de la escritura.
Toby Speight
@Toby, el valor de HISTTIMEFORMAT se usa cuando se muestra (a stdout), pero solo su estado (establecido en cualquier cosa, incluso nulo vs no establecido) es importante cuando se escribe HISTFILE.
dave_thompson_085
Gracias @dave, no lo sabía (no ser usuario de los tiempos de la historia).
Toby Speight
date -dno es portátil para decir Solaris ... ¿Asumo que esto está en un sistema con herramientas GNU en su mayoría? (GNU AWK / Perl tienden a ser los métodos más portátiles para manejar las conversiones de fechas). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimeparece no portátil ...)
Gert van den Berg

Respuestas:

6

Asumiendo un formato de archivo consistente, bashpuede leer el archivo línea por línea, probar si está en el formato dado y luego realizar la conversión:

while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && \
      date -d@"${BASH_REMATCH[1]}"; done <file.txt

BASH_REMATCHes una matriz cuyo primer elemento es el primer grupo capturado en la coincidencia de expresiones regulares =~, en este caso, la época.


Si desea mantener la estructura del archivo:

while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' \
   "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt

esto generará el contenido modificado en STDOUT, para guardarlo en un archivo, por ejemplo out.txt:

while ...; do ...; done >out.txt

Ahora, si lo desea, puede reemplazar el archivo original:

mv out.txt file.txt

Ejemplo:

$ cat file.txt
#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web

$ while IFS= read -r i; do [[ $i =~ ^#([0-9]{10})$ ]] && date -d@"${BASH_REMATCH[1]}"; done <file.txt
Wed Aug 24 20:09:55 BDT 2016
Wed Aug 24 20:11:46 BDT 2016
Wed Aug 24 20:13:58 BDT 2016

$ while IFS= read -r i; do if [[ $i =~ ^#([0-9]{10})$ ]]; then printf '#%s\n' "$(date -d@"${BASH_REMATCH[1]}")"; else printf '%s\n' "$i"; fi; done <file.txt
#Wed Aug 24 20:09:55 BDT 2016
ll /data/holding/email
#Wed Aug 24 20:11:46 BDT 2016
cat /etc/rsyslog.conf
#Wed Aug 24 20:13:58 BDT 2016
ll /data/holding/web
heemayl
fuente
Agradable ... que imprime la fecha convertida a la pantalla, ¿cómo obtengo ese comando para reemplazar las entradas en el archivo?
maquinista
@machinist Compruebe mis ediciones ..
heemayl
1
Si está utilizando una versión reciente de bash, printfpuede hacer la conversión en sí: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
chepner
14

Si bien es posible con GNU sedcon cosas como:

sed -E 's/^#([0-9]+).*$/date -d @\1/e'

Eso sería terriblemente ineficiente (y es fácil introducir vulnerabilidades arbitrarias de inyección de comandos 1 ) ya que eso significaría ejecutar un shell y un datecomando para cada #xxxxlínea, prácticamente tan malo como un while readbucle de shell . Aquí, sería mejor usar cosas como perlo gawk, es decir, utilidades de procesamiento de texto que tienen capacidades de conversión de fecha incorporadas:

perl  -MPOSIX -pe 's/^#(\d+).*/ctime $1/se'

O:

gawk '/^#/{$0 = strftime("%c", substr($0, 2))};1'

1 Si hubiéramos escrito en ^#([0-9]).*lugar de ^#([0-9]).*$(como hice en una versión anterior de esta respuesta), entonces en entornos locales de varios bytes como UTF-8 (la norma hoy en día), con una entrada como #1472047795<0x80>;reboot, donde ese <0x80>es el valor de byte 0x80 que no forma un carácter válido, ese scomando habría terminado ejecutándose date -d@1472047795<0x80>; rebootpor ejemplo. Mientras que con el extra $, esas líneas no serían sustituidas. Un enfoque alternativo sería: s/^#([0-9])/date -d @\1 #/edejar la parte después de la #xxxfecha como un comentario de shell

Stéphane Chazelas
fuente
1
¿Qué pasa con el uso de una sola instancia dedate -f para hacer todas las conversiones de una manera inteligente?
Trauma digital
El comando perl parece agregar una nueva línea después de ctime $ 1 y no puedo encontrar ninguna forma de eliminarlo.
Alex Harvey
1
@Alex. Derecha. Ver editar. Agregar la sbandera hace que eso .*también incluya la nueva línea en la entrada. También puedes usar strftime "%c", localtime $1.
Stéphane Chazelas
@ StéphaneChazelas muchas gracias. Es una gran respuesta.
Alex Harvey
3

Todas las demás respuestas generan un nuevo dateproceso para cada fecha de época que necesita convertirse. Potencialmente, esto podría agregar una sobrecarga de rendimiento si su entrada es grande.

Sin embargo, la fecha GNU tiene una -fopción práctica que permite que una sola instancia de proceso datelea continuamente las fechas de entrada sin la necesidad de una nueva bifurcación. Por lo tanto, podemos usar sed, pastey datede esta manera, que cada uno solo se genere una vez (2 veces sed) independientemente de cuán grande sea la entrada:

$ paste -d '\n' <( sed '2~2d;y/#/@/' epoch.txt | date -f - ) <( sed '1~2d' epoch.txt )
Wed Aug 24 07:09:55 PDT 2016
ll /data/holding/email
Wed Aug 24 07:11:46 PDT 2016
cat /etc/rsyslog.conf
Wed Aug 24 07:13:58 PDT 2016
ll /data/holding/web
$ 
  • Los dos sedcomandos respectivamente eliminan básicamente las líneas pares e impares de la entrada; el primero también se reemplaza #por @para dar el formato de marca de tiempo de época correcto.
  • sedLuego se canaliza la primera salida a la date -fque se realiza la conversión de fecha requerida, para cada línea de entrada que recibe.
  • Estas dos corrientes se entrelazan en la única salida requerida usando paste. Las <( )construcciones son sustituciones de procesos bash que efectivamente engañan a pegar para que piense que está leyendo los nombres de archivo dados cuando de hecho está leyendo la salida canalizada desde el comando interno. -d '\n'le dice pasteque separe las líneas de salida pares e impares con una nueva línea. Puede cambiar (o eliminar) esto si, por ejemplo, desea la marca de tiempo en la misma línea que el otro texto.

Tenga en cuenta que hay varios GNUisms y Bashisms en este comando. Esto no es compatible con Posix y no debe esperarse que sea portátil fuera del mundo GNU / Linux. Por ejemplo, date -fhace algo más en la datevariante BSD de OSX .

Trauma digital
fuente
date -d(de la pregunta) tampoco es portátil ... (En FreeBSD intentará meterse con la configuración DST, en Solaris dará un error ...) La pregunta no especifica un sistema operativo ...
Gert van den Berg
@GertvandenBerg sí, esto se aborda en el último párrafo de esta respuesta.
Trauma digital
Quiero decir que la muestra del autor de la pregunta también tiene problemas de portabilidad ... (Probablemente deberían haber etiquetado un sistema operativo ...)
Gert van den Berg
1

Suponiendo que el formato de fecha que tiene en su publicación es el que desea, la siguiente expresión regular debe ajustarse a sus necesidades.

sed -E 's/\#(1[0-9]{9})(.*)/echo \1 $(date -d @\1)/e' log.file

Tenga en cuenta el hecho de que esto solo reemplazará una época por línea.

Hatclock
fuente
Recibo el siguiente error con ese comando: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
maquinista
1
Mi error, editó la publicación.
Hatclock
0

usando sed:

sed -r 's/\#([0-9]*)/echo $(date -d @\1)/eg' test.txt

salida:

ر أغس 24 16:09:55 EET 2016
ll /data/holding/email
ر أغس 24 16:11:46 EET 2016
cat /etc/rsyslog.conf
ر أغس 24 16:13:58 EET 2016
ll /data/holding/web

como mi idioma local es el árabe :)

hassan
fuente
0

Mi solución como hacer eso en una tubería

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
kayn
fuente