Perl6:逐行读取大型压缩的gz文件

9
我正在尝试使用Perl6逐行读取一个gz文件,但是我卡住了:
  1. 如何在Perl6中逐行读取gz文件,但是,这种方法将一切内容都读入“:out”,需要的内存太多了,只适用于非常小的文件。

  2. 我不明白如何使用Perl6的Compress::Zlib逐行获取所有内容,尽管我在其GitHub上开了个问题https://github.com/retupmoca/P6-Compress-Zlib/issues/17

  3. 我正在尝试使用Perl5的Compress::Zlib来转换此代码,在Perl5中完美运行:

use Compress::Zlib;
my $file = "data.txt.gz";
my $gz = gzopen($file, "rb") or die "Error reading $file: $gzerrno";

while ($gz->gzreadline($_) > 0) {
    # Process the line read in $_
}

die "Error reading $file: $gzerrno" if $gzerrno != Z_STREAM_END ;
$gz->gzclose() ;

使用 Perl6 中的 Inline::Perl5,可以将其转换为以下内容:

use Compress::Zlib:from<Perl5>;
my $file = 'chrMT.1.vcf.gz';
my $gz = Compress::Zlib::new(gzopen($file, 'r');
while ($gz.gzreadline($_) > 0) {
  print $_;
}
$gz.gzclose();
  1. 我对 Lib::Archive 的示例https://github.com/frithnanth/perl6-Archive-Libarchive/blob/master/examples/readfile.p6感到困惑,我不知道如何获得类似于这里的第3个项目。

  2. 在 Perl6 中应该有像

for $file.IO.lines(gz) -> $line { 或者类似的东西,但是如果存在的话,我找不到它。

在 Perl6 中如何逐行读取大文件而不将所有内容都读入内存?


2
关于使用 Inline::Perl5 的问题:对于 $gz.gzreadline($_) 的调用,似乎 gzreadline 试图通过修改其输入参数 $_(被视为输出参数,但不是真正的 Perl 5 引用变量)来返回从 zip 文件中读取的行,但该值未返回到 Perl 6 脚本。 - Håkon Hægland
1
关于 Perl 5 的 gzreadline 函数的更多内容:在 Perl 5 中,您可以修改一个不是引用的输入参数,并且更改将反映在调用者中。这是通过修改特殊的 @_ 数组变量中的条目来完成的。例如:sub quote { $_[0] = "'$_[0]'" } $str = "Hello"; quote($str) 将引用 $str,即使 $str 没有通过引用传递。我不确定 Inline::Perl5 是否能够处理这些类型的输出参数。 - Håkon Hægland
关于第4项,仅澄清一下:底层库提供了一个通用的archive_read_data_block()方法,必须适用于每种存档格式中的每种文件。read-file-content方法返回一个Buf中的整个文件。要逐行读取文件,应编写自己的方法使用Archive::Libarchive::Raw,但我看到有更简单的方法,如您问题的答案所示。 - Fernando Santagata
2个回答

8

更新 现在已经进行了测试,发现了一个错误,现在已经修复。

解决方案 #2

use Compress::Zlib;

my $file   = "data.txt.gz" ;
my $handle = try open $file or die "Error reading $file: $!" ;
my $zwrap  = zwrap($handle, :gzip) ;

for $zwrap.lines {
    .print
}

CATCH { default { die "Error reading $file: $_" } }

$handle.close ;

我已经用一个小的gzip压缩的文本文件进行了测试。

我对gzip等方面不是很了解,但是根据以下内容找到了解决方法:

  • 了解P6;

  • 阅读Compress::ZlibREADME并选择zwrap例程;

  • 查看模块源代码,特别是zwrap例程的签名our sub zwrap ($thing, :$zlib, :$deflate, :$gzip);

  • 试错,主要是猜测我需要传递:gzip副词。


请评论一下我的代码是否对您有用。我猜主要问题是它是否足够快处理您的大文件。
第5种解决方案尝试失败
既然第2种解决方案可行,我本来期望只需要编写以下代码即可:
use Compress::Zlib ;
.print for "data.txt.gz".&zwrap(:gzip).lines ;

但是会失败,显示如下:

No such method 'eof' for invocant of type 'IO::Path'

这可能是因为此模块是在重新组织IO类之前编写的。

这导致我遇到了@MattOates的IO :: Handle对象,带有.lines方法的问题。我注意到没有回应,并且在https://github.com/MattOates?tab=repositories上也没有相关的存储库。


5
I am focusing on the Inline::Perl5 solution that you tried.
For the call to $gz.gzreadline($_): it seems like gzreadline tries to return the line read from the zip file by modifying its input argument $_ (treated as an output argument, but it is not a true Perl 5 reference variable[1]), but the modified value is not returned to the Perl 6 script.
Here is a possoble workaround: Create a wrapper module in the curent directory, e.g. ./MyZlibWrapper.pm:
package MyZlibWrapper;
use strict;
use warnings;
use Compress::Zlib ();
use Exporter qw(import);

our @EXPORT = qw(gzopen);
our $VERSION = 0.01;

sub gzopen {
    my ( $fn, $mode ) = @_;
    my $gz = Compress::Zlib::gzopen( $fn, $mode );
    my $self = {gz => $gz}; 
    return bless $self, __PACKAGE__;
}

sub gzreadline {
    my ( $self ) = @_;
    my $line = "";
    my $res = $self->{gz}->gzreadline($line);
    return [$res, $line];
}

sub gzclose {
    my ( $self ) = @_;
    $self->{gz}->gzclose();
}    

1;

然后在这个包装模块上使用Inline::Perl5,而不是Compress::Zlib。例如:./p.p6

use v6;
use lib:from<Perl5> '.';
use MyZlibWrapper:from<Perl5>;
my $file = 'data.txt.gz';
my $mode = 'rb';
my $gz = gzopen($file, $mode);
loop {
    my ($res, $line) = $gz.gzreadline();
    last if $res == 0;
    print $line;
}
$gz.gzclose();

在Perl 5中,您可以修改一个非引用的输入参数,并且更改将在调用者中反映出来。这是通过修改特殊的@_数组变量中的条目来完成的。例如:sub quote { $_[0] = "'$_[0]'" } $str = "Hello"; quote($str)即使$str未通过引用传递,也会引用$str

3
您可以使用lib:from<Perl5> '.';来更改Perl5使用的路径。然后,use MyZlibWrapper:from<Perl5>;就可以工作了。 - Brad Gilbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接