内容

名称

IO::Compress::FAQ -- IO::Compress 常见问题解答

描述

常见问题的解答。

通用

与 Unix compress/uncompress 的兼容性。

虽然 Compress::Zlib 有两个名为 compressuncompress 的函数,但它们与同名的 Unix 程序相关。Compress::Zlib 模块与 Unix compress 不兼容。

如果您有 uncompress 程序可用,您可以使用它来读取压缩文件

open F, "uncompress -c $filename |";
while (<F>)
{
    ...

或者,如果您有 gunzip 程序可用,您可以使用它来读取压缩文件

open F, "gunzip -c $filename |";
while (<F>)
{
    ...

以及这个来写入压缩文件,如果您有 compress 程序可用

open F, "| compress -c $filename ";
print F "data";
...
close F ;

访问 .tar.Z 文件

Archive::Tar 模块可以选择使用 Compress::Zlib(通过 IO::Zlib 模块)来访问使用 gzip 压缩的 tar 文件。不幸的是,使用 Unix compress 实用程序压缩的 tar 文件无法被 Compress::Zlib 读取,因此无法被 Archive::Tar 直接访问。

如果 uncompressgunzip 程序可用,您可以使用以下其中一种解决方法从 Archive::Tar 读取 .tar.Z 文件

首先使用 uncompress

use strict;
use warnings;
use Archive::Tar;

open F, "uncompress -c $filename |";
my $tar = Archive::Tar->new(*F);
...

以及这个使用 gunzip

use strict;
use warnings;
use Archive::Tar;

open F, "gunzip -c $filename |";
my $tar = Archive::Tar->new(*F);
...

类似地,如果 compress 程序可用,您可以使用它来写入 .tar.Z 文件

use strict;
use warnings;
use Archive::Tar;
use IO::File;

my $fh = IO::File->new( "| compress -c >$filename" );
my $tar = Archive::Tar->new();
...
$tar->write($fh);
$fh->close ;

如何使用不同的压缩方式重新压缩?

如果您意识到所有 IO::Compress::* 对象都源自 IO::File,并且所有 IO::Uncompress::* 模块都可以从 IO::File 文件句柄读取,那么这比您想象的要容易。

例如,假设您有一个使用 gzip 压缩的文件,您想用 bzip2 重新压缩它。以下是执行重新压缩所需的一切。

use IO::Uncompress::Gunzip ':all';
use IO::Compress::Bzip2 ':all';

my $gzipFile = "somefile.gz";
my $bzipFile = "somefile.bz2";

my $gunzip = IO::Uncompress::Gunzip->new( $gzipFile )
    or die "Cannot gunzip $gzipFile: $GunzipError\n" ;

bzip2 $gunzip => $bzipFile
    or die "Cannot bzip2 to $bzipFile: $Bzip2Error\n" ;

注意,此技术存在一个限制。一些压缩文件格式在压缩数据有效负载之外存储额外的信息。例如,gzip 可以选择存储原始文件名,而 Zip 存储有关原始文件的许多信息。如果原始压缩文件包含任何这些额外信息,则使用上述技术不会将其传输到新的压缩文件。

ZIP

IO::Compress::Zip & IO::Uncompress::Unzip 支持哪些压缩类型?

IO::Compress::ZipIO::Uncompress::Unzip 支持以下压缩格式:

我可以读取/写入大于 4 GB 的 Zip 文件吗?

是的,IO-Compress-ZipIO-Uncompress-Unzip 模块都支持名为 Zip64 的 zip 功能。这使它们能够读取/写入大于 4 GB 的文件/缓冲区。

如果您使用一次性接口创建 Zip 文件,并且任何输入文件都大于 4 GB,则会创建一个 zip64 兼容的 zip 文件。

zip "really-large-file" => "my.zip";

类似地,使用一次性接口,如果输入缓冲区大于 4 GB,则会创建一个 zip64 兼容的 zip 文件。

zip \$really_large_buffer => "my.zip";

一次性接口允许您通过包含 Zip64 选项来强制创建 zip64 zip 文件。

zip $filehandle => "my.zip", Zip64 => 1;

如果您想使用 OO 接口创建 zip64 zip 文件,则必须指定 Zip64 选项。

my $zip = IO::Compress::Zip->new( "whatever", Zip64 => 1 );

使用 IO-Uncompress-Unzip 解压缩时,它会自动检测 zip 文件是否为 zip64。

如果您打算使用外部 zip/unzip 操作使用 IO-Compress-Zip 创建的 Zip64 zip 文件,请确保它支持 Zip64。

特别是,如果您使用的是 Info-Zip,则需要使用 zip 版本 3.x 或更高版本来更新 Zip64 存档,并使用 unzip 版本 6.x 来读取 zip64 存档。

我可以写入超过 64K 个条目的 Zip 文件吗?

是的。Zip64 允许这样做。请参阅上一个问题。

Zip 资源

压缩文件的主要参考文档是“appnote”,可在 http://www.pkware.com/documents/casestudies/APPNOTE.TXT 找到。

另一个选择是 Info-Zip appnote。它可以在 ftp://ftp.info-zip.org/pub/infozip/doc/ 获取。

GZIP

Gzip 资源

gzip 文件的主要参考文档是 RFC 1952 https://datatracker.ietf.org/doc/html/rfc1952

gzip 的主要网站是 http://www.gzip.org

处理连接的 gzip 文件

如果 gunzip 程序遇到包含多个连接在一起的 gzip 文件的文件,它将自动解压缩所有文件。以下示例说明了这种行为。

$ echo abc | gzip -c >x.gz
$ echo def | gzip -c >>x.gz
$ gunzip -c x.gz
abc
def

默认情况下,IO::Uncompress::Gunzip 不会像 gunzip 程序那样工作。它只会解压缩文件中的第一个 gzip 数据流,如下所示。

$ perl -MIO::Uncompress::Gunzip=:all -e 'gunzip "x.gz" => \*STDOUT'
abc

要强制 IO::Uncompress::Gunzip 解压缩所有 gzip 数据流,请包含 MultiStream 选项,如下所示。

$ perl -MIO::Uncompress::Gunzip=:all -e 'gunzip "x.gz" => \*STDOUT, MultiStream => 1'
abc
def

使用 IO::Uncompress::Gunzip 读取 bgzip 文件

bgzip 文件由一系列有效的 gzip 兼容数据流连接在一起组成。要使用 IO::Uncompress::Gunzip 读取由 bgzip 创建的文件,请使用上一节中所示的 MultiStream 选项。

有关 bgzip 的定义,请参阅 http://samtools.github.io/hts-specs/SAMv1.pdf 中名为“BGZF 压缩格式”的部分。

ZLIB

Zlib 资源

zlib 压缩库的主要网站是 http://www.zlib.org

Bzip2

Bzip2 资源

bzip2 的主要网站是 http://www.bzip.org

处理连接的 bzip2 文件

如果 bunzip2 程序遇到包含多个 bzip2 文件连接在一起的文件,它将自动解压缩所有文件。以下示例说明了此行为

$ echo abc | bzip2 -c >x.bz2
$ echo def | bzip2 -c >>x.bz2
$ bunzip2 -c x.bz2
abc
def

默认情况下,IO::Uncompress::Bunzip2 不会像 bunzip2 程序那样工作。它只会解压缩文件中第一个 bzip2 数据流,如下所示

$ perl -MIO::Uncompress::Bunzip2=:all -e 'bunzip2 "x.bz2" => \*STDOUT'
abc

要强制 IO::Uncompress::Bunzip2 解压缩所有 bzip2 数据流,请包含 MultiStream 选项,如下所示

$ perl -MIO::Uncompress::Bunzip2=:all -e 'bunzip2 "x.bz2" => \*STDOUT, MultiStream => 1'
abc
def

与 Pbzip2 交互

Pbzip2 (http://compression.ca/pbzip2/) 是 bzip2 的并行实现。pbzip2 的输出由一系列连接的 bzip2 数据流组成。

默认情况下,IO::Uncompress::Bzip2 只会解压缩 pbzip2 文件中的第一个 bzip2 数据流。要解压缩完整的 pbzip2 文件,您必须包含 MultiStream 选项,如下所示。

bunzip2 $input => \$output, MultiStream => 1
    or die "bunzip2 failed: $Bunzip2Error\n";

HTTP 和网络

Apache::GZip 再探

下面是一个名为 Apache::GZip 的 mod_perl Apache 压缩模块,取自 http://perl.apache.org/docs/tutorials/tips/mod_perl_tricks/mod_perl_tricks.html#On_the_Fly_Compression

package Apache::GZip;
#File: Apache::GZip.pm

use strict vars;
use Apache::Constants ':common';
use Compress::Zlib;
use IO::File;
use constant GZIP_MAGIC => 0x1f8b;
use constant OS_MAGIC => 0x03;

sub handler {
    my $r = shift;
    my ($fh,$gz);
    my $file = $r->filename;
    return DECLINED unless $fh=IO::File->new($file);
    $r->header_out('Content-Encoding'=>'gzip');
    $r->send_http_header;
    return OK if $r->header_only;

    tie *STDOUT,'Apache::GZip',$r;
    print($_) while <$fh>;
    untie *STDOUT;
    return OK;
}

sub TIEHANDLE {
    my($class,$r) = @_;
    # initialize a deflation stream
    my $d = deflateInit(-WindowBits=>-MAX_WBITS()) || return undef;

    # gzip header -- don't ask how I found out
    $r->print(pack("nccVcc",GZIP_MAGIC,Z_DEFLATED,0,time(),0,OS_MAGIC));

    return bless { r   => $r,
                   crc =>  crc32(undef),
                   d   => $d,
                   l   =>  0
                 },$class;
}

sub PRINT {
    my $self = shift;
    foreach (@_) {
      # deflate the data
      my $data = $self->{d}->deflate($_);
      $self->{r}->print($data);
      # keep track of its length and crc
      $self->{l} += length($_);
      $self->{crc} = crc32($_,$self->{crc});
    }
}

sub DESTROY {
   my $self = shift;

   # flush the output buffers
   my $data = $self->{d}->flush;
   $self->{r}->print($data);

   # print the CRC and the total length (uncompressed)
   $self->{r}->print(pack("LL",@{$self}{qw/crc l/}));
}

1;

这是您需要使用它的 Apache 配置条目。设置后,它将导致 /compressed 目录中的所有内容自动压缩。

<Location /compressed>
   SetHandler  perl-script
   PerlHandler Apache::GZip
</Location>

虽然乍一看 Apache::GZip 中似乎有很多事情要做,但您可以将代码的功能总结如下——读取 $r->filename 中文件的内容,压缩它并将压缩后的数据写入标准输出。仅此而已。

此代码必须经过一些步骤才能实现这一点,因为

  1. Compress::Zlib 版本 1.x 中的 gzip 支持只能与真正的文件系统文件句柄一起使用。Apache 模块使用的文件句柄与文件系统无关。

  2. 这意味着所有 gzip 支持都必须手动完成——在本例中,通过创建一个绑定文件句柄来处理创建 gzip 头部和尾部。

IO::Compress::Gzip 没有这种文件句柄限制(这是最初编写它的原因之一)。因此,如果使用 IO::Compress::Gzip 而不是 Compress::Zlib,则可以删除整个绑定文件句柄代码。以下是重写的代码。

package Apache::GZip;

use strict vars;
use Apache::Constants ':common';
use IO::Compress::Gzip;
use IO::File;

sub handler {
    my $r = shift;
    my ($fh,$gz);
    my $file = $r->filename;
    return DECLINED unless $fh=IO::File->new($file);
    $r->header_out('Content-Encoding'=>'gzip');
    $r->send_http_header;
    return OK if $r->header_only;

    my $gz = IO::Compress::Gzip->new( '-', Minimal => 1 )
        or return DECLINED ;

    print $gz $_ while <$fh>;

    return OK;
}

或者更简洁地说,像这样,使用一次性 gzip

package Apache::GZip;

use strict vars;
use Apache::Constants ':common';
use IO::Compress::Gzip qw(gzip);

sub handler {
    my $r = shift;
    $r->header_out('Content-Encoding'=>'gzip');
    $r->send_http_header;
    return OK if $r->header_only;

    gzip $r->filename => '-', Minimal => 1
      or return DECLINED ;

    return OK;
}

1;

上面使用的一次性 gzip 只从 $r->filename 读取并将压缩后的数据写入标准输出。

请注意上面代码中使用 Minimal 选项。在使用 gzip 进行 Content-Encoding 时,您始终应该使用此选项。在上面的示例中,它将阻止文件名包含在 gzip 头部中,并使 gzip 数据流的大小略微减小。

压缩文件和 Net::FTP

Net::FTP 模块提供了两个低级方法,名为 storretr,它们都返回文件句柄。这些文件句柄可以与 IO::Compress/Uncompress 模块一起使用,以压缩或解压缩从 FTP 服务器读取或写入 FTP 服务器的文件,而无需创建临时文件。

首先,以下代码使用 retr 在从 FTP 服务器读取时解压缩文件。

use Net::FTP;
use IO::Uncompress::Gunzip qw(:all);

my $ftp = Net::FTP->new( ... )

my $retr_fh = $ftp->retr($compressed_filename);
gunzip $retr_fh => $outFilename, AutoClose => 1
    or die "Cannot uncompress '$compressed_file': $GunzipError\n";

以及以下代码在写入 FTP 服务器时压缩文件

use Net::FTP;
use IO::Compress::Gzip qw(:all);

my $stor_fh = $ftp->stor($filename);
gzip "filename" => $stor_fh, AutoClose => 1
    or die "Cannot compress '$filename': $GzipError\n";

MISC

使用 InputLength 解压缩嵌入在较大文件/缓冲区中的数据。

一个相当常见的用例是,压缩数据嵌入在更大的文件/缓冲区中,并且您希望读取两者。

例如,考虑 zip 文件的结构。这是一个定义明确的文件格式,它在一个文件中混合了压缩和未压缩的数据部分。

为了讨论的目的,您可以将 zip 文件视为压缩数据流的序列,每个数据流都以未压缩的本地头为前缀。本地头包含有关压缩数据流的信息,包括压缩文件的名称,特别是压缩数据流的长度。

为了说明如何使用 InputLength,以下脚本遍历 zip 文件并打印出每个压缩文件中包含的行数(如果您打算编写代码来真正遍历 zip 文件,请参阅 "在 IO::Uncompress::Unzip 中遍历 zip 文件")。此外,虽然此示例使用基于 zlib 的压缩,但该技术可以由其他 IO::Uncompress::* 模块使用。

use strict;
use warnings;

use IO::File;
use IO::Uncompress::RawInflate qw(:all);

use constant ZIP_LOCAL_HDR_SIG  => 0x04034b50;
use constant ZIP_LOCAL_HDR_LENGTH => 30;

my $file = $ARGV[0] ;

my $fh = IO::File->new( "<$file" )
            or die "Cannot open '$file': $!\n";

while (1)
{
    my $sig;
    my $buffer;

    my $x ;
    ($x = $fh->read($buffer, ZIP_LOCAL_HDR_LENGTH)) == ZIP_LOCAL_HDR_LENGTH
        or die "Truncated file: $!\n";

    my $signature = unpack ("V", substr($buffer, 0, 4));

    last unless $signature == ZIP_LOCAL_HDR_SIG;

    # Read Local Header
    my $gpFlag             = unpack ("v", substr($buffer, 6, 2));
    my $compressedMethod   = unpack ("v", substr($buffer, 8, 2));
    my $compressedLength   = unpack ("V", substr($buffer, 18, 4));
    my $uncompressedLength = unpack ("V", substr($buffer, 22, 4));
    my $filename_length    = unpack ("v", substr($buffer, 26, 2));
    my $extra_length       = unpack ("v", substr($buffer, 28, 2));

    my $filename ;
    $fh->read($filename, $filename_length) == $filename_length
        or die "Truncated file\n";

    $fh->read($buffer, $extra_length) == $extra_length
        or die "Truncated file\n";

    if ($compressedMethod != 8 && $compressedMethod != 0)
    {
        warn "Skipping file '$filename' - not deflated $compressedMethod\n";
        $fh->read($buffer, $compressedLength) == $compressedLength
            or die "Truncated file\n";
        next;
    }

    if ($compressedMethod == 0 && $gpFlag & 8 == 8)
    {
        die "Streamed Stored not supported for '$filename'\n";
    }

    next if $compressedLength == 0;

    # Done reading the Local Header

    my $inf = IO::Uncompress::RawInflate->new( $fh,
                        Transparent => 1,
                        InputLength => $compressedLength )
      or die "Cannot uncompress $file [$filename]: $RawInflateError\n"  ;

    my $line_count = 0;

    while (<$inf>)
    {
        ++ $line_count;
    }

    print "$filename: $line_count\n";
}

上面大部分代码都与读取 zip 本地头数据有关。我想重点关注的代码在底部。

while (1) {

    # read local zip header data
    # get $filename
    # get $compressedLength

    my $inf = IO::Uncompress::RawInflate->new( $fh,
                        Transparent => 1,
                        InputLength => $compressedLength )
      or die "Cannot uncompress $file [$filename]: $RawInflateError\n"  ;

    my $line_count = 0;

    while (<$inf>)
    {
        ++ $line_count;
    }

    print "$filename: $line_count\n";
}

IO::Uncompress::RawInflate 的调用创建一个新的文件句柄 $inf,该句柄可用于从父文件句柄 $fh 读取,并在读取时解压缩。使用 InputLength 选项将保证从 $fh 文件句柄读取的压缩数据最多$compressedLength 字节(唯一的例外是错误情况,例如截断文件或损坏的数据流)。

这意味着一旦 RawInflate 完成,$fh 将保留在压缩数据流之后的字节处。

现在考虑没有 InputLength 的代码是什么样的

while (1) {

    # read local zip header data
    # get $filename
    # get $compressedLength

    # read all the compressed data into $data
    read($fh, $data, $compressedLength);

    my $inf = IO::Uncompress::RawInflate->new( \$data,
                        Transparent => 1 )
      or die "Cannot uncompress $file [$filename]: $RawInflateError\n"  ;

    my $line_count = 0;

    while (<$inf>)
    {
        ++ $line_count;
    }

    print "$filename: $line_count\n";
}

这里的区别是添加了临时变量 $data。它用于存储压缩数据的副本,以便在解压缩时使用。

如果您知道 $compressedLength 不大,那么使用临时存储不会有问题。但是,如果 $compressedLength 非常大,或者您正在编写其他用户会使用的应用程序,因此不知道 $compressedLength 会有多大,那么这可能是一个问题。

使用 InputLength 可以避免使用临时存储,这意味着应用程序可以处理大型压缩数据流。

最后一点——显然,只有在您事先知道压缩数据长度的情况下才能使用 InputLength,就像这里使用 zip 文件一样。

支持

一般反馈/问题/错误报告应发送至 https://github.com/pmqs//issues(首选)或 https://rt.cpan.org/Public/Dist/Display.html?Name=

另请参阅

Compress::ZlibIO::Compress::GzipIO::Uncompress::GunzipIO::Compress::DeflateIO::Uncompress::InflateIO::Compress::RawDeflateIO::Uncompress::RawInflateIO::Compress::Bzip2IO::Uncompress::Bunzip2IO::Compress::LzmaIO::Uncompress::UnLzmaIO::Compress::XzIO::Uncompress::UnXzIO::Compress::LzipIO::Uncompress::UnLzipIO::Compress::LzopIO::Uncompress::UnLzopIO::Compress::LzfIO::Uncompress::UnLzfIO::Compress::ZstdIO::Uncompress::UnZstdIO::Uncompress::AnyInflateIO::Uncompress::AnyUncompress

IO::Compress::FAQ

File::GlobMapperArchive::ZipArchive::TarIO::Zlib

作者

此模块由 Paul Marquess 编写,[email protected]

修改历史

请参阅 Changes 文件。

版权和许可

版权所有 (c) 2005-2023 Paul Marquess。保留所有权利。

本程序是自由软件;您可以根据与 Perl 本身相同的条款重新发布和/或修改它。