
확인해 보세요:
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이 -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 시스템과 같은 Little Endian 시스템에서는 다음을 사용할 수 있습니다.
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
매뉴얼 페이지 의 추가 정보(귀하의 질문과 직접적인 관련이 없음) :
압축되지 않은 크기는 압축된 .Z 파일과 같이 gzip 형식이 아닌 파일의 경우 -1로 지정됩니다. 이러한 파일의 압축되지 않은 크기를 얻으려면 다음을 사용할 수 있습니다.
zcat file.Z | wc -c