IO::Compress::FAQ -- IO::Compress 常见问题解答
常见问题的解答。
虽然 Compress::Zlib
有两个名为 compress
和 uncompress
的函数,但它们不与同名的 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 ;
Archive::Tar
模块可以选择使用 Compress::Zlib
(通过 IO::Zlib
模块)来访问使用 gzip
压缩的 tar 文件。不幸的是,使用 Unix compress
实用程序压缩的 tar 文件无法被 Compress::Zlib
读取,因此无法被 Archive::Tar
直接访问。
如果 uncompress
或 gunzip
程序可用,您可以使用以下其中一种解决方法从 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 存储有关原始文件的许多信息。如果原始压缩文件包含任何这些额外信息,则使用上述技术不会将其传输到新的压缩文件。
IO::Compress::Zip
和 IO::Uncompress::Unzip
支持以下压缩格式:
Store(方法 0)
完全不压缩。
Deflate(方法 8)
这是使用 IO::Compress::Zip
创建 zip 文件时的默认压缩方式。
Bzip2(方法 12)
仅在安装了 IO-Compress-Bzip2
模块时才支持。
Lzma(方法 14)
仅在安装了 IO-Compress-Lzma
模块时才支持。
是的,IO-Compress-Zip
和 IO-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 存档。
是的。Zip64 允许这样做。请参阅上一个问题。
压缩文件的主要参考文档是“appnote”,可在 http://www.pkware.com/documents/casestudies/APPNOTE.TXT 找到。
另一个选择是 Info-Zip appnote。它可以在 ftp://ftp.info-zip.org/pub/infozip/doc/ 获取。
gzip 文件的主要参考文档是 RFC 1952 https://datatracker.ietf.org/doc/html/rfc1952
gzip 的主要网站是 http://www.gzip.org。
如果 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
bgzip
文件由一系列有效的 gzip 兼容数据流连接在一起组成。要使用 IO::Uncompress::Gunzip
读取由 bgzip
创建的文件,请使用上一节中所示的 MultiStream
选项。
有关 bgzip
的定义,请参阅 http://samtools.github.io/hts-specs/SAMv1.pdf 中名为“BGZF 压缩格式”的部分。
zlib 压缩库的主要网站是 http://www.zlib.org。
bzip2 的主要网站是 http://www.bzip.org。
如果 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 (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";
下面是一个名为 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
中文件的内容,压缩它并将压缩后的数据写入标准输出。仅此而已。
此代码必须经过一些步骤才能实现这一点,因为
Compress::Zlib
版本 1.x 中的 gzip 支持只能与真正的文件系统文件句柄一起使用。Apache 模块使用的文件句柄与文件系统无关。
这意味着所有 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
模块提供了两个低级方法,名为 stor
和 retr
,它们都返回文件句柄。这些文件句柄可以与 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";
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::Zlib、IO::Compress::Gzip、IO::Uncompress::Gunzip、IO::Compress::Deflate、IO::Uncompress::Inflate、IO::Compress::RawDeflate、IO::Uncompress::RawInflate、IO::Compress::Bzip2、IO::Uncompress::Bunzip2、IO::Compress::Lzma、IO::Uncompress::UnLzma、IO::Compress::Xz、IO::Uncompress::UnXz、IO::Compress::Lzip、IO::Uncompress::UnLzip、IO::Compress::Lzop、IO::Uncompress::UnLzop、IO::Compress::Lzf、IO::Uncompress::UnLzf、IO::Compress::Zstd、IO::Uncompress::UnZstd、IO::Uncompress::AnyInflate、IO::Uncompress::AnyUncompress
File::GlobMapper、Archive::Zip、Archive::Tar、IO::Zlib
此模块由 Paul Marquess 编写,[email protected]
。
请参阅 Changes 文件。
版权所有 (c) 2005-2023 Paul Marquess。保留所有权利。
本程序是自由软件;您可以根据与 Perl 本身相同的条款重新发布和/或修改它。