Altere o formato de data e hora do arquivo csv sem usar a data -d

5

Eu tenho um arquivo .csv contendo

Data1|Data2|10/24/2017 8:10:00 AM

Pretendo alterar o formato de data e hora da coluna 3 da seguinte forma:

De 10/24/2017 8:10:00 AM (12 horas) a 20171024 08:10:00 (24 horas).

Não usando -d

    
por John Christian Dela Cruz 03.11.2017 / 03:39

7 respostas

5

Uma solução awk pura (que não separa um comando date ):

awk -F'|' -vOFS='|' '
function fail() {
        printf "Bad data at line %d: ", NR
        print
        next
    }
    {
        if (split($3, date_time, " ") != 3) fail()
        if (split(date_time[1], date, "/") != 3) fail()
        if (split(date_time[2], time, ":") != 3) fail()
        if (time[1] == 12) time[1] = 0
        if (date_time[3] == "PM") time[1] += 12
        $3 = sprintf("%.4d%.2d%.2d %.2d:%.2d:%.2d", date[3], date[1], date[2], time[1], time[2], time[3])
        print
    }'
  • -F'|' divide a linha de entrada em barras verticais em $1 , $2 , $3 , etc…
  • split($3, date_time, " ") divide o campo de data / hora em três partes: a data, a hora e o indicador AM / PM. Se não houver três partes, emita uma mensagem de erro e pule a linha.
  • split(date_time[1], date, "/") divide a data no mês, no dia e no ano.
  • split(date_time[2], time, ":") divide o tempo na hora, nos minutos e nos segundos.
  • Faça um pouco de matemática na hora; por exemplo, 12:42 é 00:42 em 24 horas. E, claro, PM adiciona 12 horas.
  • O sprintf reagrupa o ano, mês, dia, hora, minutos e segundos, com zeros à esquerda, se necessário. Atribuir isso a $3 reconstrói a linha de entrada com a data / hora reformatada; nós então imprimimos isso.
  • Recurso: se a entrada tiver mais de três campos; por exemplo,

    Data1|Data2|10/24/2017 8:10:00 AM|Data4|Data5
    

    este script preservará esses campos extras.

Uso: Algumas pequenas variações:

  • Digite o comando de várias linhas acima e, no final da última linha (logo após }' ), coloque o (s) nome (s) do (s) arquivo (s) que deseja processar. Você pode (obviamente) usar curingas (por exemplo, *.csv ) aqui, além de ou em vez de nome (s) de arquivo (s).
  • O mesmo que o acima, mas depois de }' , digamos < e um nome de arquivo. (Você pode processar apenas um arquivo de cada vez dessa maneira).
  • Crie um arquivo de script.
    • A primeira linha deve ser #!/bin/sh . (Ou, se preferir, você pode usar #!/bin/bash ou #!/usr/bin/env bash . Uma discussão sobre as diferenças entre essas diferentes linhas “she-bang”, e seus méritos e contraindicações relativos, está além do escopo desta questão, mas você pode encontrar muitos discursos sobre o assunto se você pesquisar.
    • Em seguida, coloque o código acima começando na linha2.
    • No final da última linha (logo após }' ), coloque "$@" ( incluindo as aspas).
    • Salve o arquivo. Vamos supor que você chame o script gman .
    • Digite chmod +x gman .
    • Digite ./gman seguido por uma lista de nomes de arquivos e / ou curingas ou por < e um único nome de arquivo.
por 03.11.2017 / 06:31
3

Aqui está uma maneira de fazer isso, assumindo que infile é seu arquivo CSV:

#!/bin/bash

IFS='|'
while read data1 data2 datestr 
do
    newdatestr=$(date -d"$datestr" +"%Y%m%d %T")
    printf "%s|%s|%s\n" "$data1" "$data2" "$newdatestr"
done < infile
    
por 03.11.2017 / 05:14
3

com AWK :

salve o arquivo a.awk :

BEGIN{
    FS="|"
    OFS = FS
}
{
    "date -d '"$3"' +'%Y%m%d %T' " | getline l
    $3 = l
    print $0
}

e execute-o com seu arquivo csv:

awk -f a.awk file.csv

por exemplo, a saída é:

Data1|Data2|20171024 08:10:00
Data1|Data2|20171024 20:10:00
Data1|Data2|20171024 20:10:00
Data1|Data2|20171024 20:14:00
Data1|Data2|20171024 20:14:00
Data1|Data2|20171024 20:11:00
Data1|Data2|20171024 20:10:06
Data1|Data2|20171024 20:10:06
Data1|Data2|20171024 08:10:50

com este exemplo:

Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:10:00 PM
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:14:00 PM
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:11:00 PM
Data1|Data2|10/24/2017 8:10:06 PM
Data1|Data2|10/24/2017 8:10:00 PM
Data1|Data2|10/24/2017 8:10:50 AM
    
por 03.11.2017 / 05:40
2

Eu usaria perl ou qualquer idioma com interface para strptime() e strftime() :

perl -MTime::Piece -F'[|]' -lape '
  $F[2] = Time::Piece->strptime($F[2], "%m/%d/%Y %I:%M:%S %p")->
                       strftime("%Y%m%d %T");
  $_ = join "|", @F' < file.csv

Mesmo com zsh :

zmodload zsh/datetime
while IFS='|' read -rA F; do
  strftime -rs t '%m/%d/%Y %I:%M:%S %p' $F[3] &&
    strftime -s 'F[3]' '%Y%m%d %T' $t
  printf '%s\n' "${(j:|:)F}"
done < file.csv
    
por 07.11.2017 / 08:37
2

Usando o GNU date (mas não date -d ) e um shell como bash que entende as substituições do processo:

$ cat file
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:10:00 AM
Data1|Data2|10/24/2017 8:10:00 AM
$ paste -d '|' <( cut -d '|' -f -2 file ) <( date -f <( cut -d '|' -f 3 file ) +'%Y%m%d %T' )
Data1|Data2|20171024 08:10:00
Data1|Data2|20171024 08:10:00
Data1|Data2|20171024 08:10:00
Data1|Data2|20171024 08:10:00
Data1|Data2|20171024 08:10:00

A chamada para date lê as datas do comando cut , que extrai a terceira coluna | -delimited do arquivo fornecido. Ele gera uma data reformatada por linha de entrada.

Isso é então colado junto com as duas primeiras colunas usando paste .

Isso tem a desvantagem de ler o arquivo duas vezes, mas só chama date uma vez (e sem -d ).

    
por 25.05.2018 / 09:39
0

Você também pode fazer isso com dateutils , por exemplo com a seguinte entrada:

10/24/2017 8:10:00 AM
10/24/2017 8:10:00 PM
10/24/2017 8:10:00 AM
10/24/2017 8:14:00 PM
10/24/2017 8:10:00 AM
10/24/2017 8:11:00 PM
10/24/2017 8:10:06 PM
10/24/2017 8:10:00 PM
10/24/2017 8:10:50 AM

e o programa dateconv ou dateutils.dconv :

dateconv -i '%m/%d/%Y %H:%M:%S %p' -f '%Y%m%d %T' < infile

Saída:

20171024 08:10:00
20171024 20:10:00
20171024 08:10:00
20171024 20:14:00
20171024 08:10:00
20171024 20:11:00
20171024 20:10:06
20171024 20:10:00
20171024 08:10:50
    
por 18.09.2018 / 14:40
-1

Isso pode ser feito facilmente usando sed ' extended regex

Surpreende-me que ninguém tenha respondido usando sed

um forro de GNU sed :

sed -r 's/([0-9]{2})\/([0-9]{2})\/([0-9]{4})//' file_name

Aqui eu usei o regex estendido para capturar grupos

    
por 25.05.2018 / 13:12