
Проверить:
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
удаляет ноль в первой строке этого вывода:
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
Поскольку данные изменились, это уже недопустимый сжатый файл, что и приводит к ошибке.
Как отметил @coffemug, страница руководства указывает, что gzip выдаст отчет -1
для файлов, не сжатых в формате gzip. Однако входные данные больше не являются сжатым файлом влюбойформат, поэтому страница руководства в некотором смысле вводит в заблуждение: она не классифицирует это как обработку ошибок.
Дальнейшее чтение:
@wildcard указывает, что другие символы, такие как обратная косая черта, могут усугубить проблему, поскольку некоторые версии echo
будут интерпретировать обратную косую черту как экранирование и выдавать другой символ (или нет, в зависимости от обработки экранирований, применяемых к символам, не входящим в их репертуар). В случае gzip (или большинства форм сжатия) различные значения байтов одинаково вероятны, и посколькувсеНулевые значения будут опущены, в то время какнекоторыйобратные косые черты приведут к изменению данных.
Чтобы этого не произошло, не пытайтесь присваивать переменной оболочки содержимое сжатого файла. Если вы хотите это сделать, используйте более подходящий язык. Вот скрипт 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
Также существует проблема, заключающаяся в том, что оболочки, отличные от , zsh
не могут хранить байты NUL в своих переменных, которая $(...)
удаляет все конечные символы новой строки (байты 0xa) и echo
преобразует свои аргументы (если они начинаются с -
или содержат \
в зависимости от echo
реализации) и добавляет дополнительный символ новой строки.
Таким образом, даже если бы gzip -l
он мог работать с каналами, полученные им выходные данные были бы повреждены.
В системах с прямым порядком байтов (например, x86) можно использовать:
tail -c4 < file.gz | od -An -tu4
чтобы получить несжатый размер последнего фрагмента.
tail
, в отличие от gzip
может вернуться к чтению входных данных, когда он не может их найти.
решение3
Похоже, gzip
не может распознать имя файла при получении его ввода из pipe. Я провел такой тест:
$ cat file.tar.gz | gzip -tv
OK
$ gzip -tv file.tar.gz
file.tar.gz: OK
Таким образом, в первом случае gzip
не удаётся распознать имя файла, что, по-видимому, необходимо для флага -l (вы можете видеть в последнем столбце вывода, что uncompressed_name — это stdout).
Еще немного информации (не относящейся напрямую к вашему вопросу) со gzip
страницы руководства:
Несжатый размер указан как -1 для файлов не в формате gzip, таких как сжатые файлы .Z. Чтобы получить несжатый размер для такого файла, вы можете использовать:
zcat file.Z | wc -c