
查看:
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
給 shell 變數並嘗試使用echo
管道將其傳送到gzip
。但 shell 的功能會妨礙(空字元被省略)。您可以透過測試腳本看到這一點:
#!/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
在此輸出的第一行中刪除一個空值:
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 格式的文件報告 a 。但是,輸入不再是壓縮文件任何格式,因此手冊頁在某種意義上具有誤導性:它沒有將其歸類為錯誤處理。
延伸閱讀:
@wildcard 指出其他字符(例如反斜線)可能會加劇問題,因為某些版本echo
會將反斜線解釋為轉義符並產生不同的字符(或不產生不同的字符,具體取決於對不在其曲目中的字元應用轉義符的處理) 。對於 gzip(或大多數形式的壓縮)的情況,各種位元組值的可能性是相同的,並且因為全部空值將被省略,而一些反斜線將導致資料被修改。
防止這種情況的方法不是嘗試將壓縮檔案的內容指派給 shell 變數。如果您想這樣做,請使用更適合的語言。下面是一個可以計算字元頻率的 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 檔案可以連接在一起)作為小端 32 位元整數儲存在檔案的最後 4 個位元組中。
要輸出該訊息,gzip -l
請尋找檔案末尾,讀取這 4 個位元組(實際上,根據strace
,它讀取最後 8 個位元組,即 CRC 和未壓縮的大小)。
然後它會列印文件的大小和該數字。 (您會注意到給出的資訊具有誤導性,並且不會給出gunzip < file.gz | wc -c
與串聯 gzip 檔案相同的結果)。
現在,如果檔案是可尋找的,那麼它就可以工作,但是當它不是管道的情況時,它就不行了。並且gzip
不夠聰明,無法檢測到它並完全讀取文件以到達文件末尾。
現在,在以下情況:
tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
還有一個問題是 shell 無法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