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 

Ок, видимо, вход не то же самое, но он должен был быть логически. Что мне здесь не хватает? Почему рабочие версии не работают?

Эта команда

 $ tmp="$(cat tmp.csv.gz)" && echo "$tmp" | gzip -l 

присваивает содержимое tmp.csv.gz переменной оболочки и пытается использовать echo канал для gzip к 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 tmp . cs 0000020 166 000 305 226 141 157 333 066 020 206 277 367 127 034 012 014 v \0 305 226 ao 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 tmp . csv 0000020 305 226 141 157 333 066 020 206 277 367 127 034 012 014 331 240 305 226 ao 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'd-файлом, который вызывает ошибку.

Как отмечалось в @coffemug, на странице руководства указывается, что gzip сообщит -1 для файлов, не находящихся в формате gzip'd. Однако вход больше не является сжатым файлом в любом формате, поэтому страница руководства в некотором смысле вводит в заблуждение: она не классифицирует это как обработку ошибок.

Дальнейшее чтение:

  • Как использовать пустые байты в Bash?
  • Представление / цитирование NUL в командной строке

@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} ); } } 

Информация о несжатом размере файла (на самом деле из несжатого размера последнего фрагмента в виде файлов gzip может быть объединена вместе) хранится как малое число 32-разрядных 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 bytes) и что echo преобразует свои аргументы (если они начинаются с - или содержат \ зависимости на реализацию echo ) и добавляет дополнительный символ новой строки.

Поэтому, даже если gzip -l смог работать с трубами, результат, который он получит, будет поврежден.

В маленькой системе endian (например, x86) вы можете использовать:

 tail -c4 < file.gz | od -An -tu4 

чтобы получить несжатый размер последнего фрагмента.

tail , в отличие от gzip , может вернуться назад, чтобы прочитать ввод, когда он не может его искать.

Похоже, gzip не может распознать имя файла при получении его ввода из трубы. Я сделал тест следующим образом:

 $ cat file.tar.gz | gzip -tv OK $ gzip -tv file.tar.gz file.tar.gz: OK 

Таким образом, в первом случае gzip не может распознать имя файла, который, по-видимому, необходим для -l-флага (вы можете увидеть в последнем столбце вывода uncompressed_name stdout).

Дополнительная информация (не связанная напрямую с вашим вопросом) с страницы gzip man:

Несжатый размер задается как -1 для файлов не в формате gzip, таких как сжатые .Z-файлы. Чтобы получить несжатый размер для такого файла, вы можете использовать:

  zcat file.Z | wc -c