Como substituir os carimbos de data / hora da época em um arquivo por outros formatos?

10

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 datas, por exemplo:

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

..mas estou lutando para descobrir como sedpercorrer 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
maquinista
fonte
1
Para referência futura (supondo que este seja um arquivo de histórico do Bash; ele se parece com um), consulte a HISTTIMEFORMATvariável shell para controlar o formato no momento da gravação.
Toby Speight
@Talvez o valor de HISTTIMEFORMAT seja usado ao exibir (para stdout), mas apenas seu status (definido como algo nulo ou não definido) é importante ao escrever HISTFILE.
Dave_thompson_085
Obrigado @dave, eu não sabia disso (não ser um usuário da história vezes).
Toby Speight
date -dnão é portátil para dizer Solaris ... Estou assumindo que isso esteja em um sistema com ferramentas principalmente GNU? (GNU AWK / Perl tendem a ser os métodos mais portáteis para lidar com conversões de datas). gawk '{ if ($0 ~ /^#[0-9]*$/) {print strftime("%c",substr($0,2)); } else {print} }' < file( strftimeParece não-portáteis ...)
van den Berg Gert

Respostas:

6

Assumindo um formato de arquivo consistente, bashvocê pode ler o arquivo linha por linha, testar se ele está no formato especificado 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 de Regex =~, nesse caso a época.


Se você deseja 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

isso 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
heemail
fonte
Bom .... que imprime a data convertida em tela, agora como obtenho esse comando para substituir as entradas no arquivo?
precisa
@machinist Verifique minhas edições ..
heemayl
1
Se você estiver usando uma versão recente do bash, printfpode fazer a própria conversão: printf '#%(%F %H)T\n' "${BASH_REMATCH[1]}".
Chepner 30/08/16
14

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

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

Isso seria terrivelmente ineficiente (e é fácil introduzir vulnerabilidades arbitrárias de injeção de comando 1 ), pois isso significaria executar um shell e um datecomando para cada #xxxxlinha, praticamente tão ruins quanto um while readloop de shell . Aqui, seria melhor usar coisas como perlou 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 em ^#([0-9]).*vez de ^#([0-9]).*$(como fiz em uma versão anterior desta resposta), em locais de vários bytes, como UTF-8 (hoje em dia a norma), com uma entrada como #1472047795<0x80>;reboot, em que esse <0x80>é o valor de byte 0x80 que não formar um caractere válido, esse scomando acabaria sendo executado, date -d@1472047795<0x80>; rebootpor exemplo. Enquanto com o extra $, essas linhas não seriam substituídas. Uma abordagem alternativa seria:, s/^#([0-9])/date -d @\1 #/eque é deixar a parte após a #xxxdata como um comentário do shell

Stéphane Chazelas
fonte
1
Que tal usar apenas uma única instânciadate -f para fazer todas as conversões de maneira otimizada para fluxo?
Digital Trauma
O comando perl parece adicionar uma nova linha após ctime $ 1 e não consigo encontrar nenhuma maneira de removê-lo.
Alex Harvey
1
@Alex. Direita. Veja editar. A adição do ssinalizador faz com que .*também inclua a nova linha na entrada. Você também pode usar strftime "%c", localtime $1.
Stéphane Chazelas
@ StéphaneChazelas muito obrigado. É uma ótima resposta.
Alex Harvey
3

Todas as outras respostas geram um novo dateprocesso para cada data da época que precisa ser convertida. Isso pode aumentar a sobrecarga de desempenho se sua entrada for grande.

No entanto, a data GNU tem uma -fopção útil que permite que uma única instância do processo dateleia continuamente as datas de entrada sem a necessidade de um novo fork. Portanto, podemos usar sed, pastee datedessa maneira, de modo que cada um seja gerado apenas uma vez (2x por sed), independentemente do tamanho da 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 sedcomandos respectivamente excluem basicamente as linhas pares e ímpares da entrada; o primeiro também substitui #por @para fornecer o formato de data e hora correto da época.
  • A primeira sedsaída é então canalizada para a date -fqual faz a conversão de data necessária, para cada linha de entrada que ela recebe.
  • Esses dois fluxos são entrelaçados na única saída necessária usando paste. As <( )construções são substituições do processo bash que efetivamente enganam a pessoa a pensar que está lendo os nomes de arquivos fornecidos, quando na verdade está lendo a saída canalizada do comando interno. -d '\n'diz pastepara separar linhas de saída pares e ímpares com uma nova linha. Você pode alterar (ou remover) isso se, por exemplo, desejar o carimbo de data / hora na mesma linha que o outro texto.

Observe que existem vários GNUisms e Bashisms neste comando. Isso não é compatível com Posix e não se espera que seja portátil fora do mundo GNU / Linux. Por exemplo, date -ffaz outra coisa na datevariante OSXes BSD .

Trauma Digital
fonte
date -d(da pergunta) também não é portátil ... (No FreeBSD, ele tenta mexer nas configurações de horário de verão, no Solaris, gera um erro ...) A questão não especifica um sistema operacional ...
Gert van den Berg
@GertvandenBerg sim, isso é abordado no último parágrafo desta resposta.
Digital Trauma
Quero dizer que a amostra do consulente também tem problemas de portabilidade ... (Eles deveria ter marcado um sistema operacional ...)
Gert van den Berg
1

Supondo que o formato de data que você possui em sua postagem seja o que você deseja, a seguinte expressão regular deve atender às suas necessidades.

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

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

Hatclock
fonte
Estou recebendo o seguinte erro com esse comando: sed: -e expression #1, char 48: invalid reference \3 on 's' command's RHS
maquinist
1
Meu erro, editou a postagem.
Hatclock 30/08/16
0

usando sed:

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

resultado :

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

hassan
fonte
0

Minha solução como fazer isso em um pipeline

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