
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.gz
einer Shell-Variable zu und versucht, echo
diesen 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 -1
fü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 echo
Backslashs 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 -l
sucht es zum Ende der Datei und liest diese 4 Bytes (tatsächlich strace
liest 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 -c
im Fall aneinandergereihter GZIP-Dateien.)
Das funktioniert, wenn die Datei suchbar ist, aber wenn nicht, wie im Fall einer Pipe, funktioniert es nicht. Und es gzip
ist 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 zsh
keine NUL-Bytes in ihren Variablen speichern können, dass sie $(...)
alle nachfolgenden Zeilenumbruchzeichen (0xa Bytes) entfernt und dass sie echo
ihre Argumente transformiert (ob sie je nach Implementierung mit beginnen -
oder enthalten ) und ein zusätzliches Zeilenumbruchzeichen hinzufügt.\
echo
Selbst wenn also gzip -l
mit 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.
tail
kann im Gegensatz zu gzip
auf das Lesen der Eingabe zurückgreifen, wenn es diese nicht finden kann.
Antwort3
Scheint, als ob gzip
der 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 gzip
der 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 gzip
der 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