확인하다:
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
CRC 및 압축되지 않은 크기인 마지막 8바이트를 읽습니다).
그런 다음 파일 크기와 해당 숫자를 인쇄합니다. (주어진 정보는 오해의 소지가 있으며 gunzip < file.gz | wc -c
gzip 파일을 연결하는 것과 동일한 결과를 제공하지 않는다는 점에 유의하십시오 .)
이제 파일을 검색할 수 있으면 작동하지만 파이프의 경우가 아니면 작동하지 않습니다. 그리고 gzip
그것을 감지하고 파일의 끝에 도달하기 위해 파일을 완전히 읽을 만큼 똑똑하지 않습니다.
이제 다음과 같은 경우입니다.
tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l
zsh
쉘이 변수에 NUL 바이트를 저장할 수 없다는 문제도 있습니다 . $(...)
모든 후행 줄 바꿈(0xa 바이트)을 제거하고 echo
인수( 구현 에 따라 시작하거나 -
포함하는 경우)를 변환하고 추가 줄 바꿈 기호를 추가합니다.\
echo
gzip -l
따라서 파이프를 사용할 수 있더라도 수신되는 출력은 손상됩니다.
little-endian 시스템(예: 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