Wie ersetze ich Zeitstempel in einer Datei durch andere Formate?


10

Ich habe eine Datei, die Epochendaten enthält, die ich in lesbare konvertieren muss. Ich weiß bereits, wie die Datumskonvertierung durchgeführt wird, z.

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

..aber ich habe Mühe herauszufinden, wie ich seddurch die Datei gehen und alle Einträge konvertieren kann. Das Dateiformat sieht folgendermaßen aus:

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

1
Wenn Sie später darauf zurückgreifen möchten (vorausgesetzt, es handelt sich um eine Bash-Verlaufsdatei; es sieht aus wie eine), sehen Sie sich die HISTTIMEFORMATShell-Variable an, um das Format zum Zeitpunkt des Schreibens zu steuern.
Toby Speight

@Toby Der Wert von HISTTIMEFORMAT wird beim Anzeigen (auf stdout) verwendet, aber beim Schreiben von HISTFILE spielt nur der Status eine Rolle (auf Null gesetzt oder nicht gesetzt).
dave_thompson_085

Danke @dave, das wusste ich nicht (ich bin selbst kein Benutzer der Geschichte).
Toby Speight

date -dist nicht portabel, um Solaris zu sagen ... Ich gehe davon aus, dass dies auf einem System mit hauptsächlich GNU-Tools ist? (GNU AWK / Perl sind in der Regel die tragbareren Methoden für Datumskonvertierungen.) gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimescheint nicht tragbar ...)
Gert van den Berg

Antworten:


6

Unter der Annahme eines konsistenten Dateiformats bashkönnen Sie die Datei zeilenweise lesen, testen, ob sie im angegebenen Format vorliegt, und dann die Konvertierung durchführen:

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

BASH_REMATCHist ein Array, dessen erstes Element die erste erfasste Gruppe im Regex-Matching ist =~, in diesem Fall die Epoche.


Wenn Sie die Dateistruktur beibehalten möchten:

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

Dadurch wird der geänderte Inhalt an STDOUT ausgegeben, um ihn in einer Datei zu speichern, z out.txt.

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

Wenn Sie möchten, können Sie jetzt die Originaldatei ersetzen:

mv out.txt file.txt

Beispiel:

$ 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

Schön ... dass das konvertierte Datum auf dem Bildschirm gedruckt wird. Wie erhalte ich nun diesen Befehl, um die Einträge in der Datei zu ersetzen?
Maschinist

@ Maschinist Überprüfen Sie meine Änderungen ..
heemayl

1
Wenn Sie eine aktuelle Version von verwenden bash, printfkönnen Sie die Konvertierung selbst durchführen : printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
Chepper

14

Während es mit GNU sedmit Dingen wie möglich ist:

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

Das wäre furchtbar ineffizient (und es ist einfach, willkürliche Sicherheitsanfälligkeiten bezüglich der Befehlsinjektion 1 einzuführen ), da dies bedeuten würde, eine Shell und einen dateBefehl für jede #xxxxZeile auszuführen, praktisch so schlecht wie eine Shell- while readSchleife . Hier ist es besser, Dinge wie perloder zu verwenden gawk, dh Textverarbeitungsprogramme mit integrierten Datumskonvertierungsfunktionen:

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

Oder:

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

1 Wenn wir ^#([0-9]).*statt ^#([0-9]).*$(wie in einer früheren Version dieser Antwort) geschrieben hätten, dann in Multi-Byte-Gebietsschemas wie UTF-8 (heutzutage die Norm) mit einer Eingabe wie #1472047795<0x80>;reboot, wobei dies <0x80>der Byte-Wert 0x80 ist bildet kein gültiges Zeichen, dieser sBefehl wäre date -d@1472047795<0x80>; rebootbeispielsweise ausgeführt worden. Mit dem Extra $würden diese Zeilen nicht ersetzt. Ein alternativer Ansatz wäre: s/^#([0-9])/date -d @\1 #/eDas heißt, das Teil nach dem #xxxDatum als Shell-Kommentar belassen


1
Wie wäre es , wenn Sie nur eine einzige Instanz von verwendendate -f , um alle Konvertierungen in Stream-Form durchzuführen?
Digitales Trauma

Der Perl-Befehl scheint nach ctime $ 1 eine neue Zeile hinzuzufügen, und ich kann sie nicht entfernen.
Alex Harvey

1
@Alex. Richtig. Siehe Bearbeiten. Durch Hinzufügen des sFlags wird .*auch die neue Zeile bei der Eingabe eingefügt. Sie können auch verwenden strftime "%c", localtime $1.
Stéphane Chazelas

@ StéphaneChazelas vielen Dank. Es ist eine großartige Antwort.
Alex Harvey

3

Alle anderen Antworten führen zu einem neuen dateProzess für jedes Epochendatum, das konvertiert werden muss. Dies kann möglicherweise zu einem zusätzlichen Leistungsaufwand führen, wenn Ihre Eingabe groß ist.

Das GNU-Datum verfügt jedoch über eine praktische -fOption, mit der eine einzelne Prozessinstanz dateEingabedaten kontinuierlich lesen kann, ohne dass eine neue Verzweigung erforderlich ist. Also wir nutzen können sed, pasteund dateauf diese Weise , so dass jeder bekommt einmal (2x für nur gelaicht sed) , unabhängig davon , wie groß die Eingabe:

$ 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
$ 
  • Die beiden sedBefehle löschen grundsätzlich gerade und ungerade Zeilen der Eingabe; die erste ersetzt auch #mit @dem richtigen Unix - Zeitstempel - Format zu geben.
  • Die erste sedAusgabe wird dann weitergeleitet, an date -fdie für jede empfangene Eingabezeile die erforderliche Datumskonvertierung durchgeführt wird.
  • Diese beiden Streams werden dann mit in die einzelne erforderliche Ausgabe verschachtelt paste. Die <( )Konstrukte sind Bash-Prozess-Ersetzungen , die das Einfügen effektiv dazu verleiten, zu glauben, dass es aus bestimmten Dateinamen liest, wenn es tatsächlich die Ausgabe liest, die vom Befehl im Inneren weitergeleitet wird. -d '\n'erzählt pastemit einem Newline ungeraden und geraden Ausgangsleitungen zu trennen. Sie können dies ändern (oder entfernen), wenn Sie beispielsweise möchten, dass sich der Zeitstempel in derselben Zeile wie der andere Text befindet.

Beachten Sie, dass dieser Befehl mehrere GNUisms und Bashisms enthält. Dies ist nicht Posix-konform und sollte außerhalb der GNU / Linux-Welt nicht portabel sein. Zum Beispiel date -fmacht etwas anderes auf OSXes BSD- dateVariante.


date -d(aus der Frage) ist auch nicht portierbar ... (Unter FreeBSD wird versucht, mit den Sommerzeiteinstellungen herumzuspielen, unter Solaris wird ein Fehler ausgegeben ...) Die Frage gibt jedoch kein Betriebssystem an ...
Gert van den Berg

@GertvandenBerg ja, dies wird im letzten Absatz dieser Antwort angesprochen.
Digitales Trauma

Ich meine, dass das Beispiel des Fragestellers auch Portabilitätsprobleme hat ... (Sie hätten wahrscheinlich ein Betriebssystem markieren sollen ...)
Gert van den Berg

1

Angenommen, das Datumsformat, das Sie in Ihrem Beitrag haben, ist das, was Sie möchten, sollte der folgende reguläre Ausdruck Ihren Anforderungen entsprechen.

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

Beachten Sie, dass dies nur eine Epoche pro Zeile ersetzt.


Ich erhalte den folgenden Fehler mit diesem Befehl: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
Maschinist

1
Mein Fehler, den Beitrag bearbeitet.
Hatclock

0

mit sed:

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

Ausgabe :

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

da meine Landessprache Arabisch ist :)


0

Meine Lösung, wie man das in einer Pipeline macht

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.