Comment remplacer les horodatages d'époque dans un fichier avec d'autres formats?


10

J'ai un fichier qui contient des dates d'époque que j'ai besoin de convertir en lisibles par l'homme. Je sais déjà comment faire la conversion de date, par exemple:

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

..mais j'ai du mal à comprendre comment sedparcourir le fichier et convertir toutes les entrées. Le format de fichier ressemble à ceci:

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

1
Pour référence future (en supposant qu'il s'agit d'un fichier d'historique Bash, il en ressemble un), recherchez la HISTTIMEFORMATvariable shell pour contrôler le format au moment de l'écriture.
Toby Speight

@Toby la valeur de HISTTIMEFORMAT est utilisée lors de l'affichage (sur stdout), mais seul son statut (défini sur n'importe quoi, même nul ou non défini) importe lors de l'écriture de HISTFILE.
dave_thompson_085

Merci @dave, je ne le savais pas (ne pas être moi-même un utilisateur de l'histoire).
Toby Speight du

date -dn'est pas portable pour dire Solaris ... Je suppose que c'est sur un système avec principalement des outils GNU? (GNU AWK / Perl ont tendance à être les méthodes les plus portables pour gérer les conversions de dates). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimesemble non portable ...)
Gert van den Berg

Réponses:


6

En supposant un format de fichier cohérent, bashvous pouvez lire le fichier ligne par ligne, tester s'il est dans un format donné, puis effectuer la conversion:

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

BASH_REMATCHest un tableau dont le premier élément est le premier groupe capturé dans la correspondance Regex,, =~dans ce cas, l'époque.


Si vous souhaitez conserver la structure du fichier:

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

cela affichera le contenu modifié dans STDOUT, pour l'enregistrer dans un fichier, par exemple out.txt:

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

Maintenant, si vous le souhaitez, vous pouvez remplacer le fichier d'origine:

mv out.txt file.txt

Exemple:

$ 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

Nice .... qui imprime la date convertie à l'écran, maintenant comment puis-je obtenir cette commande pour remplacer les entrées dans le fichier?
machiniste

@machinist Vérifiez mes modifications ..
heemayl

1
Si vous utilisez une version récente de bash, printfpeut faire la conversion elle - même: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
chepner

14

Bien que cela soit possible avec GNU sedavec des choses comme:

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

Ce serait terriblement inefficace (et il est facile d'introduire des vulnérabilités arbitraires d'injection de commandes 1 ) car cela signifierait exécuter un shell et une datecommande pour chaque #xxxxligne, pratiquement aussi mauvais qu'une while readboucle shell . Ici, il serait préférable d'utiliser des choses comme perlou gawk, c'est-à-dire des utilitaires de traitement de texte qui ont des capacités de conversion de date intégrées:

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

Ou:

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

1 Si nous avions écrit à la ^#([0-9]).*place de ^#([0-9]).*$(comme je l'ai fait dans une version antérieure de cette réponse), alors dans des paramètres régionaux à plusieurs octets comme ceux en UTF-8 (la norme de nos jours), avec une entrée comme #1472047795<0x80>;reboot, où c'est <0x80>la valeur d'octet 0x80 qui ne forme pas un caractère valide, cette scommande aurait fini par s'exécuter date -d@1472047795<0x80>; rebootpar exemple. Avec le supplément $, ces lignes ne seraient pas remplacées. Une approche alternative serait: s/^#([0-9])/date -d @\1 #/elaisser la partie après la #xxxdate sous forme de commentaire shell


1
Que diriez-vous d'utiliser une seule instance dedate -f pour effectuer toutes les conversions de manière rationnelle?
Digital Trauma

La commande perl semble ajouter une nouvelle ligne après ctime $ 1 et je ne trouve aucun moyen de la supprimer.
Alex Harvey

1
@Alex. Droite. Voir modifier. L'ajout du sdrapeau fait en sorte que .*la nouvelle ligne soit également entrée. Vous pouvez également utiliser strftime "%c", localtime $1.
Stéphane Chazelas

@ StéphaneChazelas merci beaucoup. C'est une excellente réponse.
Alex Harvey

3

Toutes les autres réponses engendrent un nouveau dateprocessus pour chaque date d'époque qui doit être convertie. Cela pourrait potentiellement ajouter des frais généraux de performances si votre entrée est volumineuse.

Cependant, GNU date a une -foption pratique qui permet à une seule instance de processus de datelire en continu les dates d'entrée sans avoir besoin d'un nouveau fork. Nous pouvons donc utiliser sed, pasteet datede cette manière, que chacun ne soit généré qu'une seule fois (2x pour sed), quelle que soit la taille de l'entrée:

$ 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
$ 
  • Les deux sedcommandes suppriment respectivement les lignes paires et impaires de l'entrée; le premier remplace également #par @pour donner le format d'horodatage correct.
  • La première sedsortie est ensuite dirigée vers date -flaquelle effectue la conversion de date requise, pour chaque ligne d'entrée qu'elle reçoit.
  • Ces deux flux sont ensuite entrelacés dans la seule sortie requise à l'aide de paste. Les <( )constructions sont des substitutions de processus bash qui incitent efficacement le collage à penser qu'il lit à partir de noms de fichiers donnés alors qu'il lit en fait la sortie canalisée depuis la commande à l'intérieur. -d '\n'indique pastede séparer les lignes de sortie impaires et paires avec une nouvelle ligne. Vous pouvez modifier (ou supprimer) cela si, par exemple, vous voulez que l'horodatage soit sur la même ligne que l'autre texte.

Notez qu'il y a plusieurs GNUisms et Bashisms dans cette commande. Ce n'est pas compatible Posix et ne devrait pas être portable en dehors du monde GNU / Linux. Par exemple, date -ffait autre chose sur la datevariante OSXes BSD .


date -d(à partir de la question) est également non portable ... (Sur FreeBSD, il essaiera de jouer avec les paramètres DST, sur Solaris, il donnera une erreur ...) La question ne spécifie cependant pas de système d'exploitation ...
Gert van den Berg

@GertvandenBerg oui, cela est traité dans le dernier paragraphe de cette réponse.
Digital Trauma

Je veux dire que l'échantillon du demandeur a également des problèmes de portabilité ... (Ils auraient probablement dû taguer un système d'exploitation ...)
Gert van den Berg

1

En supposant que le format de date que vous avez dans votre message soit ce que vous voulez, l'expression régulière suivante devrait répondre à vos besoins.

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

Gardez à l'esprit que cela ne remplacera qu'une seule époque par ligne.


J'obtiens l'erreur suivante avec cette commande: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
machinist

1
Mon erreur, a édité le post.
Hatclock

0

en utilisant sed:

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

production :

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

comme ma langue locale est l'arabe :)


0

Ma solution comment faire cela dans un pipeline

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/`date -d @\1`/; s/$/"/' | bash
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.