
チェックアウト:
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
どうやら入力は同じではないようですが、論理的には同じであるはずです。ここで何が欠けているのでしょうか? パイプされたバージョンが機能しないのはなぜですか?
答え1
このコマンド
$ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
の内容をtmp.csv.gz
シェル変数に割り当て、 を使用してecho
それを にパイプしようとしgzip
ます。しかし、シェルの機能が邪魔をします (ヌル文字は省略されます)。これはテスト スクリプトで確認できます。
#!/bin/sh
tmp="$(cat tmp.csv.gz)" && echo "$tmp" |cat >foo.gz
cmp foo.gz tmp.csv.gz
さらに少し作業して、od
(またはhexdump
) を使用して 2 つのファイルを詳しく調べます。例:
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
この出力の最初の行の null を削除します。
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
データが変更されたため、有効な gzip 圧縮ファイルではなくなり、エラーが発生します。
@coffemugが指摘したように、マニュアルページには、gzipは-1
gzip形式でないファイルに対しては を報告すると記載されています。しかし、入力はもはや圧縮ファイルではありません。どれでも形式なので、マニュアル ページはある意味で誤解を招く可能性があります。つまり、これをエラー処理として分類していません。
参考文献:
@wildcard は、バックスラッシュなどの他の文字が問題を引き起こす可能性があることを指摘していますecho
。これは、 のバージョンによっては、バックスラッシュをエスケープとして解釈し、別の文字を生成するためです (または、エスケープの扱いによっては、レパートリーにない文字に適用されるエスケープの扱いによって異なります)。gzip (またはほとんどの圧縮形式) の場合、さまざまなバイト値は同じ可能性で、全てnullは省略されますが、いくつかのバックスラッシュを使用するとデータが変更されます。
これを防ぐには、圧縮ファイルの内容をシェル変数に割り当てないようにします。そうしたい場合は、より適切な言語を使用してください。例として、文字の頻度をカウントできる Perl スクリプトを次に示します。
#!/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} );
}
}
答え2
ファイルの非圧縮サイズに関する情報 (gzip ファイルは連結できるため、実際には最後のチャンクの非圧縮サイズ) は、ファイルの最後の 4 バイトにリトル エンディアンの 32 ビット整数として保存されます。
その情報を出力するには、gzip -l
ファイルの末尾までシークし、その 4 バイトを読み取ります (実際には、 によるとstrace
、最後の 8 バイト、つまり CRC と非圧縮サイズを読み取ります)。
次に、ファイルのサイズとその数値を出力します。(提供される情報は誤解を招くものであり、gunzip < file.gz | wc -c
連結された gzip ファイルの場合と同じ結果が得られないことに注意してください)。
さて、これはファイルがシーク可能な場合には機能しますが、パイプの場合のようにシーク不可能な場合には機能しません。また、gzip
それを検出してファイルを完全に読み取ってファイルの最後まで到達できるほどスマートではありません。
さて、次の場合:
tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
また、 以外のシェルでは変数zsh
に NUL バイトを格納できない、$(...)
末尾の改行文字 (0xa バイト) がすべて削除される、引数(実装に応じて でecho
始まる-
か を含む場合) が変換され、余分な改行文字が追加されるという問題もあります。\
echo
したがって、gzip -l
パイプを操作できたとしても、受信する出力は破損します。
リトルエンディアン システム (x86 など) では、以下を使用できます。
tail -c4 < file.gz | od -An -tu4
最後のチャンクの非圧縮サイズを取得します。
tail
とは対照的に、gzip
入力を探すことができない場合は、フォールバックして入力を読み取ることができます。
答え3
gzip
パイプから入力を取得するときにファイル名を認識できないようです。次のようなテストを実行しました:
$ cat file.tar.gz | gzip -tv
OK
$ gzip -tv file.tar.gz
file.tar.gz: OK
したがって、最初のケースではgzip
、-l フラグに必要なファイル名を認識できません (出力の最後の列で uncompressed_name が stdout であることがわかります)。
gzip
man ページからの詳細情報 (質問に直接関係ありません) :
圧縮された .Z ファイルなど、gzip 形式ではないファイルの場合、非圧縮サイズは -1 として返されます。このようなファイルの非圧縮サイズを取得するには、次を使用します。
zcat file.Z | wc -c