'last'从外部命令阅读文件时perl崩溃

c3frrgcw  于 2022-11-15  发布在  Perl
关注(0)|答案(1)|浏览(150)

下面的代码:

#!/usr/bin/env perl

use 5.0360;
use warnings FATAL => 'all';
use autodie ':default';
use Devel::Confess 'color'; # not essential, but better error reporting

open my $view, "zcat a.big.file.vcf.gz|"; # zcat or bcftools
while (<$view>) {
    next unless /^#CHROM\t/;
    last;
}
close $view;

上面的代码崩溃并出现错误

Can't close(GLOB(0x55adfa96ebf8)) filehandle: '' at (eval 11)[/home/con/perl5/perlbrew/perls/perl-5.36.0/lib/5.36.0/Fatal.pm:1683] line 74
 at (eval 11)[/home/con/perl5/perlbrew/perls/perl-5.36.0/lib/5.36.0/Fatal.pm:1683] line 74.
    main::__ANON__[(eval 11)[/home/con/perl5/perlbrew/perls/perl-5.36.0/lib/5.36.0/Fatal.pm:1683]:86](GLOB(0x55adfa96ebf8)) called at mwe.pl line 13
Command exited with non-zero status 255

然而,如果我注解掉last,代码运行时不会出现问题,但是,文件是 * 巨大的 *,这在运行时间上有很大的不同。
如果我删除close $view,代码也可以工作,但close是正确的做法。
如何同时使用lastclose $view运行代码?

bq3bfh9z

bq3bfh9z1#

当您last阅读该进程(此处为zcat)和close管道时,在该进程完成写入之前,该进程将获得一个SIGPIPE
如果在向管道另一端写入数据的进程完成写入之前关闭管道的读取端,则会导致写入程序收到SIGPIPE
因此,当close然后wait s时,它得到一个非零值,并返回false(如下所示)。仅此而已。剩下的--程序“崩溃”--直到autodie,它抛出一个异常。如果没有autodie(或致命警告)

use warnings;
use strict;
use feature 'say';

use Scalar::Util qw(openhandle);

my $file = shift // die "Usage: $0 file\n";

open my $view, "zcat $file |" or die "Can't pipe-open zcat: $!";

while (<$view>) {
    next unless /^#CHROM\t/;  # (added to my test file)
    say "Matched --> $_";
    last;
}

say "Filehandle good? --> ", openhandle($view) // 'undef';  # GLOB(...)
    
close $view or warn $!  
    ? "Error closing pipe: $!" 
    : "Command exit status: $?";                            # 13

say "Program received a signal ", $? & 127  if $? & 127;    # 13

我得到了Command exit status: 13,所以close没有返回true,而$!为false。
如果文件句柄来自一个管道打开,如果其他系统调用失败或其程序以非零状态退出,则close返回false。如果唯一的问题是程序以非零状态退出,则$!将被设置为0
我们确实得到了一个信号,13(对于sigpipe,请参见man 7 signal),它终止了程序,因此没有特定的退出代码($? >> 8确实是零),也没有内核被转储($? & 128是零)。
由于退出状态为非零,close返回false,并且autodie抛出其异常。
那么这件事该怎么办?
当然,那个close必须留下来检查。
即使发送到zcatSIGPIPE可以被忽略,正如我在一些文档中看到的那样,您也不会希望这样做--它是故意放在那里的,让编写者知道没有读取器,以便它可以停止!
最后,是autodie终止程序,并且可以按词法禁用它。(这满足了注解中对autodie的“需要”。)因此,将此管道阅读和early close放在一个块中

READ_FROM_PIPE: { 
    no autodie qw(close);
    # do the pipe-open, read from the pipe, close() it...
};
# rest of code

不要忘记适当地调整此代码中的其他错误处理。
(我有过no autodie“泄漏”超出其范围的奇怪经历,请参见here。但那是另一种情况,并由autodie 2.30修复,所以希望不用担心。)
另一个选择是将所有这些,或者仅仅是close(), Package 在eval中。这被认为是一个很好的实践,他们在文档中说。然后看看如何处理autodie异常。

相关问题