gzip mesma saída de entrada diferente

1

Confira:

data/tmp$ gzip -l tmp.csv.gz
     compressed        uncompressed  ratio uncompressed_name
           2846               12915  78.2% tmp.csv
data/tmp$ cat tmp.csv.gz | gzip -l
     compressed        uncompressed  ratio uncompressed_name
             -1                  -1   0.0% stdout
data/tmp$ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l

gzip: stdin: unexpected end of file

Ok, aparentemente a entrada não é a mesma, mas deveria ter sido, logicamente. O que estou perdendo aqui? Por que as versões canalizadas não estão funcionando?

    
por gwn 23.06.2016 / 10:13

3 respostas

4

Este comando

$ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l

atribui o conteúdo de tmp.csv.gz a uma variável shell e tenta usar echo para canalizar isso para gzip . Mas os recursos do shell ficam no caminho (caracteres nulos são omitidos). Você pode ver isso por um script de teste:

#!/bin/sh
tmp="$(cat tmp.csv.gz)" && echo "$tmp" |cat >foo.gz
cmp foo.gz tmp.csv.gz

e com mais algum trabalho, usando od (ou hexdump ) e olhando de perto os dois arquivos. Por exemplo:

0000000 037 213 010 010 373 242 153 127 000 003 164 155 160 056 143 163
        037 213  \b  \b 373 242   k   W  
0000000 037 213 010 010 373 242 153 127 003 164 155 160 056 143 163 166
        037 213  \b  \b 373 242   k   W 003   t   m   p   .   c   s   v
0000020 305 226 141 157 333 066 020 206 277 367 127 034 012 014 331 240
        305 226   a   o 333   6 020 206 277 367   W 034  \n  \f 331 240
0000040 110 246 145 331 362 214 252 230 143 053 251 121 064 026 152 027
          H 246   e 331 362 214 252 230   c   + 251   Q   4 026   j 027
003 t m p . c s 0000020 166 000 305 226 141 157 333 066 020 206 277 367 127 034 012 014 v
#!/usr/bin/perl -w

use strict;

our %counts;

sub doit() {
    my $file = shift;
    my $fh;
    open $fh, "$file" || die "cannot open $file: $!";
    my @data = <$fh>;
    close $fh;
    for my $n ( 0 .. $#data ) {
        for my $o ( 0 .. ( length( $data[$n] ) - 1 ) ) {
            my $c = substr( $data[$n], $o, 1 );
            $counts{$c} += 1;
        }
    }
}

while ( $#ARGV >= 0 ) {
    &doit( shift @ARGV );
}

for my $c ( sort keys %counts ) {
    if ( ord $c > 32 && ord $c < 127 ) {
        printf "%s:%d\n", $c, $counts{$c} if ( $counts{$c} );
    }
    else {
        printf "\%03o:%d\n", ord $c, $counts{$c} if ( $counts{$c} );
    }
}
305 226 a o 333 6 020 206 277 367 W 034 \n \f 0000040 331 240 110 246 145 331 362 214 252 230 143 053 251 121 064 026 331 240 H 246 e 331 362 214 252 230 c + 251 Q 4 026

elimina um nulo na primeira linha desta saída:

$ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l

Como os dados mudam, não é mais um arquivo gzip'd válido, o que produz o erro.

Como observado por @coffemug, a página de manual aponta que o gzip irá reportar um -1 para arquivos que não estão no formato gzip'd. No entanto, a entrada não é mais um arquivo compactado no formato any , de modo que a página de manual é enganosa: ela não a categoriza como tratamento de erros.

Leitura adicional:

@wildcard aponta que outros caracteres, como barra invertida, podem ser adicionados ao problema, porque algumas versões de echo interpretarão uma barra invertida como um escape e produzirão um caractere diferente (ou não, dependendo do tratamento das fugas aplicadas a personagens não em seu repertório). Para o caso de gzip (ou a maioria das formas de compressão), os vários valores de byte são igualmente prováveis, e como todos os nulos serão omitidos, enquanto que algumas barras invertidas causarão os dados para ser modificado.

A maneira de evitar isso não é tentar atribuir uma variável shell ao conteúdo de um arquivo compactado. Se você quiser fazer isso, use uma linguagem mais adequada. Aqui está um script Perl que pode contar frequências de caracteres, como um exemplo:

#!/bin/sh
tmp="$(cat tmp.csv.gz)" && echo "$tmp" |cat >foo.gz
cmp foo.gz tmp.csv.gz
    
por 23.06.2016 / 10:57
2

As informações sobre o tamanho descompactado do arquivo (na verdade, do tamanho descompactado do último fragmento como arquivos gzip podem ser concatenados juntos) são armazenadas como um pequeno inteiro de 32 bits endian nos últimos 4 bytes do arquivo.

Para produzir essa informação, gzip -l procura no final do arquivo, lê esses 4 bytes (na verdade, de acordo com strace , ele lê os últimos 8 bytes, ou seja, o CRC e o tamanho não compactado). / p>

Em seguida, imprime o tamanho do arquivo e esse número. (você notará que as informações fornecidas são enganosas e não dariam o mesmo resultado que gunzip < file.gz | wc -c no caso de arquivos gzip concatenados).

Agora, isso funciona se o arquivo for procurado, mas quando não é como no caso de um pipe, não. E gzip não é inteligente o suficiente para detectá-lo e ler o arquivo totalmente para chegar ao final do arquivo.

Agora, no caso de:

tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l

Há também o problema de que shells diferentes de zsh não podem armazenar bytes NUL em suas variáveis, que $(...) retira todos os caracteres de nova linha (0xa bytes) e que echo transforma seus argumentos (se eles começarem com - ou contém \ dependendo da implementação echo ) e adiciona um caractere extra de nova linha.

Assim, mesmo que gzip -l consiga trabalhar com pipes, a saída que receberia seria corrompida.

Em um pequeno sistema endian (como o x86), você pode usar:

tail -c4 < file.gz | od -An -tu4

para obter o tamanho não compactado do último bloco.

tail , ao contrário de gzip , pode voltar a ler a entrada quando não pode procurá-la.

    
por 23.06.2016 / 22:36
0

Parece que gzip não pode reconhecer o nome do arquivo ao obter sua entrada do pipe. Eu fiz um teste assim:

$ cat file.tar.gz | gzip -tv 
  OK

$ gzip -tv file.tar.gz
  file.tar.gz: OK

Portanto, no primeiro caso gzip é incapaz de reconhecer o nome do arquivo que parece ser necessário para o sinalizador -l (você pode ver na última coluna de saída uncompressed_name é stdout).

Mais algumas informações (não relacionadas diretamente à sua pergunta) da página gzip man:

The uncompressed size is given as -1 for files not in gzip format, such as compressed .Z files. To get the uncompressed size for such a file, you can use:

     zcat file.Z | wc -c
    
por 23.06.2016 / 10:23

Tags