gzip тот же вход, другой выход

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

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

решение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

Связанный контент