gzip 同じ入力ですが、異なる出力

gzip 同じ入力ですが、異なる出力

確認する:

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)を使用して両方のファイルを詳しく見てください。たとえば、

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が-1gzip以外の形式のファイルについて報告することを示しています。ただし、入力はもはや圧縮ファイルではありません。どの形式なので、マニュアルページはある意味誤解を招く。マニュアルページはこれをエラー処理として分類しません。

追加資料:

@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バイトを読みます(実際には、straceCRCと圧縮されていないサイズの最後の8バイトを読みます)。

次に、ファイルサイズとその数を印刷します。 (与えられた情報は誤解を招く可能性があり、gunzip < file.gz | wc -cgzipファイルをリンクするのと同じ結果を提供しないことに注意してください。)

これでファイルを検索できる場合は機能しますが、パイプの場合以外は機能しません。そして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マニュアルページの追加情報(あなたの質問とは直接関係ありません):

gzip以外の形式のファイル(圧縮された.Zファイルなど)の場合、圧縮されていないサイズは-1です。これらのファイルの圧縮されていないサイズを取得するには、次のものを使用できます。

     zcat file.Z | wc -c

関連情報