Come sostituire i timestamp in un file con altri formati?


10

Ho un file che contiene date di epoca che devo convertire in leggibili dall'uomo. So già come eseguire la conversione della data, ad esempio:

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

..ma mi sto sforzando di capire come arrivare sednel file e convertire tutte le voci. Il formato del file è simile al seguente:

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

1
Per riferimento futuro (supponendo che si tratti di un file di cronologia di Bash; sembra uno di questi), guarda la HISTTIMEFORMATvariabile shell per controllare il formato al momento della scrittura.
Toby Speight,

@Toby il valore di HISTTIMEFORMAT viene utilizzato quando si visualizza (su stdout), ma solo il suo stato (impostato su qualsiasi valore anche nullo o non impostato) è importante quando si scrive HISTFILE.
dave_thompson_085,

Grazie @dave, non lo sapevo (non essendo un utente della storia volte me stesso).
Toby Speight,

date -dnon è portatile dire Solaris ... Suppongo che questo sia su un sistema con strumenti per lo più GNU? (GNU AWK / Perl tendono ad essere i metodi più portatili per gestire le conversioni di date). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimesembra non portatile ...)
Gert van den Berg

Risposte:


6

Supponendo che il formato del file sia coerente, bashpuoi leggere il file riga per riga, verificare se è in un determinato formato e quindi eseguire la conversione:

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

BASH_REMATCHè un array il cui primo elemento è il primo gruppo acquisito nella corrispondenza Regex =~, in questo caso l'epoca.


Se si desidera mantenere la struttura del file:

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

questo produrrà i contenuti modificati su STDOUT, per salvarli in un file, ad esempio out.txt:

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

Ora, se lo desideri, puoi sostituire il file originale:

mv out.txt file.txt

Esempio:

$ 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

Bello .... che stampa la data convertita sullo schermo, ora come posso ottenere quel comando per sostituire le voci nel file?
macchinista il

@machinist Controlla le mie modifiche ..
heemayl

1
Se si utilizza una versione recente di bash, printfpuò fare la conversione in sé: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
Chepner,

14

Mentre è possibile con GNU sedcon cose come:

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

Sarebbe terribilmente inefficiente (ed è facile introdurre vulnerabilità arbitrarie nell'iniezione di comandi 1 ) poiché ciò significherebbe eseguire una shell e un datecomando per ogni #xxxxriga, praticamente come un while readloop di shell . Qui, sarebbe meglio usare cose come perlo gawk, cioè utilità di elaborazione del testo con funzionalità di conversione della data integrate:

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

O:

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

1 Se avessimo scritto ^#([0-9]).*invece di ^#([0-9]).*$(come ho fatto in una versione precedente di questa risposta), quindi in locali multibyte come UTF-8 (la norma al giorno d'oggi), con un input simile #1472047795<0x80>;reboot, dove si <0x80>trova il valore byte 0x80 che non forma un carattere valido, quel scomando sarebbe finito date -d@1472047795<0x80>; rebootper esempio. Mentre con l'extra $, quelle linee non verrebbero sostituite. Un approccio alternativo sarebbe:, s/^#([0-9])/date -d @\1 #/ecioè lasciare la parte dopo la #xxxdata come commento shell


1
Che ne dici di usare solo una singola istanza didate -f fare tutte le conversioni in modo streaming?
Trauma digitale il

Il comando perl sembra aggiungere una nuova riga dopo ctime $ 1 e non riesco a trovare alcun modo per rimuoverlo.
Alex Harvey,

1
@Alex. Giusto. Vedi modifica. L'aggiunta del sflag rende tale da .*includere anche la nuova riga sull'input. Puoi anche usare strftime "%c", localtime $1.
Stéphane Chazelas,

@ StéphaneChazelas grazie mille. È un'ottima risposta
Alex Harvey,

3

Tutte le altre risposte generano un nuovo dateprocesso per ogni data di epoca che deve essere convertita. Questo potrebbe potenzialmente aggiungere un sovraccarico prestazionale se l'input è grande.

Tuttavia, la data GNU ha un'utile -fopzione che consente a una singola istanza di processo di dateleggere continuamente le date di input senza la necessità di un nuovo fork. Quindi possiamo usare sed, pastee datein questo modo in modo che ognuno venga generato una sola volta (2x per sed) indipendentemente dalla dimensione dell'input:

$ 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
$ 
  • I due sedcomandi rispettivamente eliminano sostanzialmente le linee pari e dispari dell'input; il primo sostituisce anche #con @per fornire il formato di data e ora corretto.
  • sedViene quindi reindirizzato il primo output a date -fcui esegue la conversione della data richiesta, per ogni riga di input che riceve.
  • Questi due flussi vengono quindi interlacciati nel singolo output richiesto utilizzando paste. I <( )costrutti sono sostituzioni di processo bash che inducono effettivamente incollare nel pensare che stia leggendo da determinati nomi di file quando in realtà sta leggendo l'output convogliato dal comando all'interno. -d '\n'indica pastedi separare le linee di output pari e dispari con una nuova riga. È possibile modificarlo (o rimuoverlo) se, ad esempio, si desidera il timestamp sulla stessa riga dell'altro testo.

Nota che ci sono diversi GNUismi e Bashismi in questo comando. Questo non è conforme a Posix e non dovrebbe essere previsto che sia portatile al di fuori del mondo GNU / Linux. Ad esempio, date -ffa qualcos'altro sulla datevariante OSD BSD .


date -d(dalla domanda) è anche non portatile ... (Su FreeBSD proverà a pasticciare con le impostazioni dell'ora legale, su Solaris darà un errore ...) La domanda non specifica un sistema operativo però ...
Gert van den Berg

@GertvandenBerg sì, questo è affrontato nell'ultimo paragrafo di questa risposta.
Trauma digitale

Voglio dire che anche il campione del richiedente presenta problemi di portabilità ... (Probabilmente avrebbero dovuto etichettato un sistema operativo ...)
Gert van den Berg

1

Supponendo che il formato della data che hai nel tuo post sia quello che vuoi, la seguente regex dovrebbe adattarsi alle tue esigenze.

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

Ricorda che questo sostituirà solo un'epoca per riga.


Ricevo il seguente errore con quel comando: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
macchinista il

1
Il mio errore, ha modificato il post.
Hatclock,

0

usando sed:

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

produzione :

ر أغس 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

poiché la mia lingua locale è l'arabo :)


0

La mia soluzione su come farlo in una pipeline

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
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.