gzip mesma entrada saída diferente

gzip mesma entrada saída diferente

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?

Responder1

Este comando

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

atribui o conteúdo de tmp.csv.gza uma variável de shell e tenta usá echo-lo para canalizá-lo para gzip. Mas os recursos do shell atrapalham (os caracteres nulos são omitidos). Você pode ver isso por meio de 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 trabalho, usando od(ou hexdump) e observando atentamente 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  \0 003   t   m   p   .   c   s
0000020 166 000 305 226 141 157 333 066 020 206 277 367 127 034 012 014
          v  \0 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

descarta um nulo na primeira linha desta saída:

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

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

Conforme observado por @coffemug, a página de manual indica que o gzip reportará um -1para arquivos que não estão no formato gzip. No entanto, a entrada não é mais um arquivo compactado emqualquerformato, portanto a página de manual é, de certa forma, enganosa: ela não categoriza isso como tratamento de erros.

Leitura adicional:

@wildcard aponta que outros caracteres como barra invertida podem aumentar o problema, porque algumas versões de echointerpretarão uma barra invertida como um escape e produzirão um caractere diferente (ou não, dependendo do tratamento de escapes aplicado a caracteres que não estão em seu repertório) . Para o caso do gzip (ou da maioria das formas de compactação), os vários valores de bytes são igualmente prováveis, e comotodosnulos serão omitidos, enquantoalgunsbarras invertidas farão com que os dados sejam modificados.

A maneira de evitar isso não é tentar atribuir a uma variável shell o 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, por exemplo:

#!/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} );
    }
}

Responder2

As informações sobre o tamanho descompactado do arquivo (na verdade, o tamanho descompactado do último pedaço, já que os arquivos gzip podem ser concatenados) são armazenadas como um número inteiro little endian de 32 bits nos últimos 4 bytes do arquivo.

Para gerar essa informação, gzip -lele busca o final do arquivo, lê esses 4 bytes (na verdade, segundo strace, ele lê os últimos 8 bytes, ou seja, o CRC e o tamanho descompactado).

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 -cno caso de arquivos gzip concatenados).

Agora, isso funciona se o arquivo for pesquisável, mas quando não é, como no caso de um pipe, isso não acontece. E gzipnão é inteligente o suficiente para detectá-lo e ler o arquivo completamente 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 outros shells zshnão podem armazenar bytes NUL em suas variáveis, que $(...)remove todos os caracteres de nova linha finais (bytes 0xa) e echotransforma seus argumentos (se eles começam -ou contêm, \dependendo da echoimplementação) e adiciona um caractere de nova linha extra .

Portanto, mesmo que gzip -lfosse capaz de trabalhar com pipes, a saída recebida seria corrompida.

Em um sistema little endian (como os x86), você pode usar:

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

para obter o tamanho não compactado do último pedaço.

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

Responder3

Parece que gzipnão é possível reconhecer o nome do arquivo ao obter sua entrada do pipe. Fiz um teste assim:

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

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

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

Mais algumas informações (não diretamente relacionadas à sua pergunta) na gzippágina de manual:

O tamanho descompactado é fornecido como -1 para arquivos que não estão no formato gzip, como arquivos .Z compactados. Para obter o tamanho descompactado desse arquivo, você pode usar:

     zcat file.Z | wc -c

informação relacionada