gzip gleiche Eingabe andere Ausgabe

gzip gleiche Eingabe andere Ausgabe

Kasse:

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, offensichtlich ist die Eingabe nicht dieselbe, aber logischerweise hätte sie es sein sollen. Was übersehe ich hier? Warum funktionieren die weitergeleiteten Versionen nicht?

Antwort1

Dieser Befehl

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

weist den Inhalt von tmp.csv.gzeiner Shell-Variable zu und versucht, echodiesen mit nach weiterzuleiten gzip. Dabei stehen jedoch die Fähigkeiten der Shell im Weg (Nullzeichen werden weggelassen). Sie können dies anhand eines Testskripts sehen:

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

und mit etwas mehr Arbeit, indem Sie od(oder hexdump) verwenden und die beiden Dateien genau betrachten. Zum Beispiel:

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

gibt in der ersten Zeile dieser Ausgabe einen Nullwert aus:

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

Da sich die Daten ändern, handelt es sich nicht mehr um eine gültige gzip-Datei, was den Fehler verursacht.

Wie von @coffemug angemerkt, weist die Manualpage darauf hin, dass gzip -1für Dateien, die nicht im gzip-Format vorliegen, eine Meldung ausgibt. Die Eingabe ist jedoch keine komprimierte Datei mehr inbeliebigFormat, daher ist die Manualpage in gewisser Weise irreführend: Sie kategorisiert dies nicht als Fehlerbehandlung.

Weiterführende Literatur:

@wildcard weist darauf hin, dass andere Zeichen wie Backslashs das Problem verstärken können, da einige Versionen von echoBackslashs als Escapezeichen interpretieren und ein anderes Zeichen erzeugen (oder auch nicht, je nachdem, wie Escapezeichen auf Zeichen angewendet werden, die nicht in ihrem Repertoire sind). Im Fall von gzip (oder den meisten Komprimierungsformen) sind die verschiedenen Bytewerte gleich wahrscheinlich, und daalleNullen werden weggelassen, währendmancheBackslashes führen zu einer Änderung der Daten.

Um dies zu verhindern, sollten Sie nicht versuchen, einer Shell-Variable den Inhalt einer komprimierten Datei zuzuweisen. Wenn Sie das tun möchten, verwenden Sie eine besser geeignete Sprache. Hier ist als Beispiel ein Perl-Skript, das Zeichenhäufigkeiten zählen kann:

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

Antwort2

Die Informationen zur unkomprimierten Größe der Datei (tatsächlich zur unkomprimierten Größe des letzten Blocks, da GZIP-Dateien aneinandergereiht werden können) werden als 32-Bit-Integer im Little-Endian-Format in den letzten 4 Bytes der Datei gespeichert.

Um diese Informationen auszugeben, gzip -lsucht es zum Ende der Datei und liest diese 4 Bytes (tatsächlich straceliest es laut die letzten 8 Bytes, also den CRC und die unkomprimierte Größe).

Anschließend werden die Größe der Datei und diese Zahl gedruckt. (Sie werden feststellen, dass die angegebenen Informationen irreführend sind und nicht dasselbe Ergebnis liefern wie gunzip < file.gz | wc -cim Fall aneinandergereihter GZIP-Dateien.)

Das funktioniert, wenn die Datei suchbar ist, aber wenn nicht, wie im Fall einer Pipe, funktioniert es nicht. Und es gzipist nicht intelligent genug, um sie zu erkennen und die Datei vollständig zu lesen, um ans Ende der Datei zu gelangen.

Nun, im Fall von:

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

Es besteht außerdem das Problem, dass andere Shells als diese zshkeine NUL-Bytes in ihren Variablen speichern können, dass sie $(...)alle nachfolgenden Zeilenumbruchzeichen (0xa Bytes) entfernt und dass sie echoihre Argumente transformiert (ob sie je nach Implementierung mit beginnen -oder enthalten ) und ein zusätzliches Zeilenumbruchzeichen hinzufügt.\echo

Selbst wenn also gzip -lmit Pipes gearbeitet werden könnte, wäre die Ausgabe beschädigt.

Auf einem Little-Endian-System (wie x86-Systemen) können Sie Folgendes verwenden:

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

um die unkomprimierte Größe des letzten Blocks zu erhalten.

tailkann im Gegensatz zu gzipauf das Lesen der Eingabe zurückgreifen, wenn es diese nicht finden kann.

Antwort3

Scheint, als ob gzipder Dateiname nicht erkannt werden kann, wenn die Eingabe über die Pipe erfolgt. Ich habe einen Test wie diesen durchgeführt:

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

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

Daher kann im ersten Fall gzipder Dateiname nicht erkannt werden, was für das Flag -l erforderlich zu sein scheint (Sie können in der letzten Spalte der Ausgabe sehen, dass uncompressed_name stdout ist).

Einige weitere Informationen (nicht direkt mit Ihrer Frage verbunden) von gzipder Manpage:

Die unkomprimierte Größe wird für Dateien, die nicht im GZIP-Format vorliegen, wie z. B. komprimierte .Z-Dateien, als -1 angegeben. Um die unkomprimierte Größe für eine solche Datei zu erhalten, können Sie Folgendes verwenden:

     zcat file.Z | wc -c

verwandte Informationen