gzip misma entrada salida diferente

gzip misma entrada salida diferente

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.gza una variable de shell e intenta usarlo echopara 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á -1archivos 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 echointerpretará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 -lbusca 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 -cen 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 gzipno 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 zshno pueden almacenar bytes NUL en sus variables, $(...)eliminan todos los caracteres de nueva línea finales (bytes 0xa) y echotransforman sus argumentos (si comienzan con -o contienen \según la echoimplementación) y agregan un carácter de nueva línea adicional. .

Entonces, incluso si gzip -lpudiera 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, gzippuede recurrir a leer la entrada cuando no puede buscarla.

Respuesta3

Parece que gzipno 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 gzipno 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 gzipla 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

información relacionada