Analisa a saída com larguras de colunas dinâmicas e campos vazios

0

O gdrive tem um subcomando list , que imprime uma lista de arquivos, como no exemplo a seguir:

gdrive list

Saída:

Id                                  Name                      Type   Size     Created
1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5   info.pdf                  bin    10.0 B   2018-08-27 20:26:20
1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl   2018-12-ss-scalettapass   dir             2018-08-27 20:26:19

Estou tentando analisar essa saída usando ferramentas como awk e sed sem sucesso.

Os problemas são "campos" vazios na coluna de tamanho e as larguras dinâmicas das colunas.

Alguém tem uma ideia de como analisar essa saída?

    
por Ray 27.08.2018 / 21:45

2 respostas

2

o awk pode lidar com dados de largura fixa. Primeiro, precisamos determinar as larguras das colunas:

fieldwidths=$(head -n 1 file | grep -Po '\S+\s*' | awk '{printf "%d ", length($0)}')

Este valor é "36 26 7 9 7 " - o último campo é maior que 7 caracteres. Vamos arbitrariamente torná-lo 70 caracteres:

fieldwidths=${fieldwidths/% /0}

Agora, vamos ler os dados e transformá-los em CSV:

awk -v FIELDWIDTHS="$fieldwidths" '{
    for (i=1; i<=NF; i++) {
        val = $i
        sub(/ *$/, "", val)
        gsub(/"/, "\"\"", val)
        printf "%s\"%s\"", (i==1 ? "" : ","), val
    }
    print ""
}' file

saídas:

"Id","Name","Type","Size","Created"
"1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5","info.pdf","bin","10.0 B","2018-08-27 20:26:20"
"1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl","2018-12-ss-scalettapass","dir","","2018-08-27 20:26:19"

A mesma funcionalidade com perl

perl -lne '
    if ($. == 1) {
        @head = ( /(\S+\s*)/g );
        pop @head;
        $patt = "^";
        $patt .= "(.{" . length($_) . "})" for @head;
        $patt .= "(.*)\$";
    }
    print join ",", map {s/"/""/g; s/\s+$//; qq("$_")} (/$patt/o);
' file
    
por 27.08.2018 / 22:31
1

Você pode fazer isso com Perl usando a função unpack criando o modelo de descompactação dinamicamente examinando o cabeçalho (1ª linha):

perl -lpe '
    $fmt //= join "", map("A" . length(), /\H+\h+(?=\H)/g), "A*";
    $_ = join ",", map { s/"/""/gr =~ s/(.*)/"$1"/r } unpack $fmt;
' input-file.txt

Explicação:

  • -p fará perl consumir o arquivo por linha. Cada linha, também conhecida como registro, é referida como $_ . Outro efeito de -p é autoprintar o registro atual antes de ir buscar o próximo.
  • -l faz 2 coisas, define ORS = RS = \n
  • O regex /\H+\h+(?=\H)/g deve buscar todos os campos, exceto o último e, em seguida, eles são alimentados para map .
  • map calcula os comprimentos desses campos e prefixos um "A" para cada um.
  • Em vez de não selecionar o último campo acima, adicionamos um pega-tudo "A *".
  • Estes são então passados para join , que os une em uma string usando o delimitador nulo. Portanto, o formato de desempacotamento está pronto para uso e não é computado novamente devido ao operador //= , que é a função defined-or .
  • Agora, armados com o formato de descompactação criado dinamicamente, aplicamos a todas as linhas, incluindo o cabeçalho.
  • unpack descompacta uma string, no nosso caso, a linha atual, usando o formato fornecido e emite os campos descompactados.
  • Esses campos emitidos são inseridos em map , que opera um por um e executa as etapas descritas no código { ... } . Em nosso caso, em cada campo fazemos o seguinte: a) dobre as aspas duplas. b) enrole o campo com aspas duplas.
  • Depois que map terminar de editar os campos, eles serão lançados em join , que os une usando a vírgula , para formar um belo arquivo CSV .
  • PS: Observe que não precisamos aparar os espaços em branco finais nos campos gerados por unpack , coz, unpack faz isso para você ao usar o A (A para ASCII) personagem de formatação.

Saída:

"Id","Name","Type","Size","Created"
"1sV3_a1ySV0-jbLxhA8NIEts1KU_aWa-5","info.pdf","bin","10.0 B","2018-08-27 20:26:20"
"1h-j3B5OLryp6HkeyTsd9PJaAtKK_GYyl","2018-12-ss-scalettapass","dir","","2018-08-27 20:26:19"

Isso pode ser feito pela ferramenta sed , mas precisaria de uma abordagem de duas passagens, em que, primeiro, usando a linha de cabeçalho da entrada, geramos um script sed dinamicamente, que então opera na entrada arquivo (incluindo o cabeçalho também) para executar a operação desejada, como mostrado:

if="input-file.txt"
cmd=$(< "$if" head -n 1 | perl -lne 'print join $/, reverse map { $s += length();qq[s/./\n/$s] } /\H+\h+(?=\H)/g')
sed -e '
    '"${cmd}"'
    s/"/""/g
    s/[[:blank:]]*\n/","/g
    s/.*/"&"/
' < "$if"
    
por 28.08.2018 / 01:57