
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.gz
a 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 -1
para 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 echo
interpretarã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 -l
ele 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 -c
no 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 gzip
nã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 zsh
não podem armazenar bytes NUL em suas variáveis, que $(...)
remove todos os caracteres de nova linha finais (bytes 0xa) e echo
transforma seus argumentos (se eles começam -
ou contêm, \
dependendo da echo
implementação) e adiciona um caractere de nova linha extra .
Portanto, mesmo que gzip -l
fosse 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 gzip
nã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, gzip
nã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 gzip
pá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