Como substituir marcas de data e hora de época em um arquivo com outros formatos?

9

Eu tenho um arquivo que contém datas de época que eu preciso converter para legível por humanos. Eu já sei como fazer a conversão de data, por exemplo:

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

.. mas estou lutando para descobrir como obter sed para percorrer o arquivo e converter todas as entradas. O formato do arquivo é assim:

#1472047795
ll /data/holding/email
#1472047906
cat /etc/rsyslog.conf
#1472048038
ll /data/holding/web
    
por machinist 30.08.2016 / 09:38

6 respostas

6

Assumindo um formato de arquivo consistente, com bash , você pode ler o arquivo linha por linha, testar se está no formato determinado e fazer a conversão:

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

BASH_REMATCH é uma matriz cujo primeiro elemento é o primeiro grupo capturado na correspondência Regex, =~ , neste caso, a época.

Se você quiser manter a estrutura do arquivo:

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

isto produzirá o conteúdo modificado para STDOUT, para salvá-lo em um arquivo, por exemplo out.txt :

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

Agora, se desejar, você pode substituir o arquivo original:

mv out.txt file.txt

Exemplo:

$ 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
    
por 30.08.2016 / 09:53
14

Embora seja possível com o GNU sed com coisas como:

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

Isso seria terrivelmente ineficiente (e é fácil introduzir vulnerabilidades arbitrárias de injeção de comandos 1 ), o que significa executar um shell e um comando date para cada linha #xxxx , virtualmente como < um mau como% shell while read . Aqui, seria melhor usar itens como perl ou gawk , ou seja, utilitários de processamento de texto com recursos de conversão de data incorporados:

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

Ou:

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

1 Se tivéssemos escrito ^#([0-9]).* em vez de ^#([0-9]).*$ (como eu fiz em uma versão anterior desta resposta), então em locais de vários bytes como os UTF-8 (a norma hoje em dia), com uma entrada como #1472047795<0x80>;reboot , em que <0x80> é o valor de byte 0x80 que não forma um caractere válido, esse comando s teria acabado executando date -d@1472047795<0x80>; reboot , por exemplo. Enquanto com o $ extra, essas linhas não seriam substituídas. Uma abordagem alternativa seria: s/^#([0-9])/date -d @ #/e , ou seja, deixe a parte após o #xxx date como um comentário de shell

    
por 30.08.2016 / 10:33
2

Todas as outras respostas geram um novo processo date para cada data de época que precisa ser convertida. Isso poderia adicionar uma sobrecarga de desempenho se sua entrada for grande.

No entanto, a data do GNU tem uma conveniente opção -f que permite que uma única instância de processo de date leia continuamente as datas de entrada sem a necessidade de uma nova bifurcação. Portanto, podemos usar sed , paste e date dessa maneira, de modo que cada um receba apenas spawn uma vez (2x para sed ), independentemente de quão grande seja a 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
$ 
  • Os dois comandos sed basicamente apagam as linhas pares e ímpares da entrada; o primeiro também substitui # por @ para fornecer o formato de data e hora de época correto.
  • A primeira sed output é então canalizada para date -f , que faz a conversão de data necessária, para cada linha de entrada que recebe.
  • Esses dois fluxos são então entrelaçados no único resultado necessário usando paste . As construções de <( ) são substituições do processo bash que efetivamente enganam e colam no pensamento está lendo de nomes de arquivos dados quando, na verdade, está lendo a saída canalizada do comando interno. -d '\n' informa paste para separar linhas de saída ímpares e pares com uma nova linha. Você poderia alterar (ou remover) isso se, por exemplo, você quiser o registro de data e hora na mesma linha que o outro texto.

Note que existem vários GNUisms e Bashisms neste comando. Isto não é compatível com o Posix e não se espera que seja portável fora do mundo GNU / Linux. Por exemplo date -f faz outra coisa em OSXes BSD date variant.

    
por 30.08.2016 / 22:49
1

Supondo que o formato de data que você tem em sua postagem é o desejado, o seguinte regex deve atender às suas necessidades.

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

Lembre-se de que isso substituirá apenas uma época por linha.

    
por 30.08.2016 / 09:48
0

usando sed:

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

saída:

ر أغس 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 meu idioma local é o árabe:)

    
por 30.08.2016 / 10:07
0

Minha solução como fazer isso em um pipeline

cat test.txt | sed 's/^/echo "/; s/\([0-9]\{10\}\)/'date -d @'/; s/$/"/' | bash
    
por 13.01.2017 / 08:50