直接从Perl进行Gzipping数据

Perl可以通过其IO层读取和写入gzip流。Nicholas Clark最近更新了PerlIO::gzip(包含来自Zefram的补丁),自上次发布以来已有九年。现在它支持Perl v5.20和即将推出的v5.22,尽管在Windows上仍然存在问题。但正如我们习惯的那样,做这件事的方法不止一种。

管道方式

Perl功能强大,就像Unix胶带一样,从标准文件句柄中读取或写入很容易。你可能知道关于三个参数的open,但我可以给它提供任意多的参数。对于管道打开,我可以将模式设置为第二个参数,将命令作为列表传递,就像在system中做的那样(参见Mastering Perl)的“安全编程章节”。我记得在哪里放-|旁边,命令会在这里

$ENV{PATH} = '';

open my $z, '-|', '/usr/bin/gunzip', '-c', 'moby_dick.txt.gz';

while( <$z> ) {
    print;
    }

close $z 
    or die "There was a problem with the pipe open!";

我也可以反过来,通过管道将打印输出到命令,该命令会为我gzip数据。在-翻转到了|的另一边,我使用shell重定向将gzip的结果移动到一个文件。我不使用列表形式,因为我希望命令中的>是特殊的(如果gzip有一个设置输出文件名的开关就好了)

$ENV{PATH} = '';

open my $z, '|-', '/usr/bin/gzip > data.gz';

while(  ) {
    print { $z } $_;
    }

close $z 
    or die "There was a problem with the pipe open!";

这是我可以用任何命令使用的通用形式。它有多个进程和对外部命令的依赖等缺点。如果我可以直接在Perl进程中完成,我就没有这些缺点。幸运的是,我可以做到,因为Perl就是这样。

读取gzip数据

在Perl中读取gzip文件,我可以用gzip I/O层(参见perlopen)。一旦打开文件,我就可以像读取“普通”文本文件一样读取其行(假设它是文本)

use PerlIO::gzip;
open my $fh, '<:gzip', $filename 
    or die "Could not read from $filename: $!";

while( <$fh> ) {
    print;
    }

或者,如果数据不是文本,我可以读取字节

use PerlIO::gzip;
open my $fh, '<:gzip', $filename 
    or die "Could not read from $filename: $!";

while( read( $fh, $buffer, 1024 ) ) {
    ...; # do something with $buffer (... is a v5.12 feature!)
    }

如果我不能使用I/O层,也许是因为操作系统不支持它或者在我的Perl版本上它坏了,我可以使用IO::Compress模块代替。此示例使用其对象接口创建写入文件句柄

use IO::Compress::Gunzip;

my $z = IO::Compress::Gunzip->new( $filename )
    or die "Could not read from $filename: $GunzipError";

while( <$z> ) {
    print;
    }

I/O层比模块快,但PerlIO文档指出我们不应该信任它。人们一直在使用它而没有遇到主要问题,但你可能是那个丢失所有数据的人。Sinan Ünür在Large gzipped files, long lines, extracting columns etc中写了关于性能的内容。

写入gzip数据

我还可以直接将gzip数据写入文件。这与我之前的示例类似,只是文件句柄的位置改变了。这个例子使用I/O层

open my $fh, '>:gzip' $filename 
    or die "Could not write to $filename: $!";

while(  ) {
    print { $fh } $_;
    }

这个例子使用IO::Compress::Gzip

use IO::Compress::Gzip;

my $z = IO::Compress::Gzip->new( $filename )
    or die "Could not write to $filename: $GzipError";

while(  ) {
    print { $z } $_;
    }

一个高级技巧

我可以用单个文件句柄读取多个gzip数据流。在IO::Compress::Gunzip中的MultiStream选项允许解压缩器在认为它检测到新的流时重置自己,并继续提供输出

use IO::Uncompress::Gunzip qw($GunzipError);

my $z = IO::Uncompress::Gunzip->new( *STDIN, MultiStream => 1 )
    or die "Could not make uncompress object: $GunzipError";
    
while( <$z> ) {
    print;
    }

这样我可以同时读取多个gzip文件

$ cat *.gz |  ./multistream.pl

当我想读取所有已旋转的日志,而不关心它们是否被分割时,这类事情非常有用。

还有一个小优惠

如果您想了解更多关于gzip压缩的信息,Julia Evans在《乌鸦》上创建了一个非常好的gzip实时工作动画,您可以观看:《乌鸦》中的gzip动画

在www.data-compression.com上您可以看到更抽象的动画。您可以看到这种单遍方法是如何工作的,以及它如何像我在这篇文章中提供的那样从可能无限的数据流中工作。


这篇文章最初发布在PerlTricks.com上。

标签

brian d foy

brian d foy是一位Perl培训师和作家,同时也是Perl.com的高级编辑。他是《精通Perl》、《Mojolicious Web Clients》、《Learning Perl Exercises》的作者,以及《Programming Perl》、《Learning Perl》、《Intermediate Perl》和《Effective Perl Programming》的合著者。

浏览他们的文章

反馈

如果您觉得这篇文章有问题,请帮助我们通过在GitHub上打开一个issue或pull request来解决。