
Verificar:
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 al parecer el input no es el mismo, pero así debería haber sido, lógicamente. ¿Que me estoy perdiendo aqui? ¿Por qué no funcionan las versiones canalizadas?
Respuesta1
Este comando
$ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
asigna el contenido de tmp.csv.gz
a una variable de shell e intenta usarlo echo
para canalizarlo a gzip
. Pero las capacidades del shell se interponen (se omiten los caracteres nulos). Puedes ver esto mediante un script de prueba:
#!/bin/sh
tmp="$(cat tmp.csv.gz)" && echo "$tmp" |cat >foo.gz
cmp foo.gz tmp.csv.gz
y con un poco más de trabajo, usando od
(o hexdump
) y mirando de cerca los dos archivos. Por ejemplo:
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
arroja un valor nulo en la primera línea de esta salida:
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
Dado que los datos cambian, ya no es un archivo gzip válido, lo que produce el error.
Como señaló @coffemug, la página del manual señala que gzip informará -1
archivos que no estén en formato gzip. Sin embargo, la entrada ya no es un archivo comprimido encualquierformato, por lo que la página del manual es en cierto sentido engañosa: no clasifica esto como manejo de errores.
Otras lecturas:
@wildcard señala que otros caracteres como la barra invertida pueden agravar el problema, porque algunas versiones echo
interpretarán una barra invertida como un escape y producirán un carácter diferente (o no, dependiendo del tratamiento de los escapes aplicados a los caracteres que no están en su repertorio). . Para el caso de gzip (o la mayoría de las formas de compresión), los distintos valores de bytes son igualmente probables y, dado quetodoLos valores nulos se omitirán, mientras quealgunoLas barras invertidas harán que se modifiquen los datos.
La forma de evitar esto es no intentar asignar a una variable de shell el contenido de un archivo comprimido. Si desea hacer eso, utilice un lenguaje más adecuado. Aquí hay un script Perl que puede contar frecuencias de caracteres, como ejemplo:
#!/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} );
}
}
Respuesta2
La información sobre el tamaño sin comprimir del archivo (en realidad, el tamaño sin comprimir del último fragmento, ya que los archivos gzip se pueden concatenar) se almacena como un entero little endian de 32 bits en los últimos 4 bytes del archivo.
Para generar esa información, gzip -l
busca hasta el final del archivo, lee esos 4 bytes (en realidad, según strace
, lee los últimos 8 bytes, es decir, el CRC y el tamaño sin comprimir).
Luego imprime el tamaño del archivo y ese número. (notarás que la información proporcionada es engañosa y no daría el mismo resultado que gunzip < file.gz | wc -c
en el caso de archivos gzip concatenados).
Ahora, eso funciona si el archivo se puede buscar, pero cuando no lo es, como en el caso de una tubería, no lo es. Y gzip
no es lo suficientemente inteligente como para detectarlo y leer el archivo por completo para llegar al final del mismo.
Ahora bien, en el caso de:
tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
También existe el problema de que otros shells zsh
no pueden almacenar bytes NUL en sus variables, $(...)
eliminan todos los caracteres de nueva línea finales (bytes 0xa) y echo
transforman sus argumentos (si comienzan con -
o contienen \
según la echo
implementación) y agregan un carácter de nueva línea adicional. .
Entonces, incluso si gzip -l
pudiera trabajar con tuberías, la salida que recibiría estaría dañada.
En un sistema little endian (como los x86), puedes usar:
tail -c4 < file.gz | od -An -tu4
para obtener el tamaño sin comprimir del último fragmento.
tail
, al contrario, gzip
puede recurrir a leer la entrada cuando no puede buscarla.
Respuesta3
Parece que gzip
no se puede reconocer el nombre del archivo cuando se ingresa desde la tubería. Hice una prueba como esta:
$ cat file.tar.gz | gzip -tv
OK
$ gzip -tv file.tar.gz
file.tar.gz: OK
Entonces, en el primer caso gzip
no se puede reconocer el nombre del archivo que parece ser necesario para el indicador -l (puede ver en la última columna del resultado uncompressed_name es stdout).
Más información (no directamente relacionada con su pregunta) de gzip
la página de manual:
El tamaño sin comprimir es -1 para archivos que no están en formato gzip, como los archivos .Z comprimidos. Para obtener el tamaño sin comprimir de dicho archivo, puede utilizar:
zcat file.Z | wc -c