perl中管道文件句柄的问题

Posted

技术标签:

【中文标题】perl中管道文件句柄的问题【英文标题】:Problem with piped filehandle in perl 【发布时间】:2010-08-13 12:47:01 【问题描述】:

我正在尝试从另一个 perl 脚本运行 bp_genbank2gff3.pl(biooperl 包) 获取 genbank 作为参数。

这不起作用(不生成输出文件):

   my $command = "bp_genbank2gff3.pl -y -o /tmp $ARGV[0]";

   open( my $command_out, "-|", $command );
   close $command_out;

但这确实

   open( my $command_out, "-|", $command );
   sleep 3; # why do I need to sleep?
   close $command_out;

为什么?

我认为close 应该阻止,直到命令完成:

关闭任何管道文件句柄会导致 父进程等待 孩子完成... (见http://perldoc.perl.org/functions/open.html)。

编辑

我在最后一行添加了这个:

say "ret=$ret, \$?=$?, \$!=$!";

在这两种情况下,打印输出都是:

ret=, $?=13, $!=

(这意味着close 在这两种情况下都失败了,对吧?)

【问题讨论】:

close() 的返回值是多少?什么是 $? 或者 $!bp_genbank2gff3.pl$ARGV[0] 的 shell 扩展是否分叉并退出? stracetruss 说发生了什么?您确定“工作”案例中的输出文件不是从不相关的成功工作中遗留下来的吗?您可以使用一些常用的 shell 实用程序而不是 bp_...3.pl 来重现有问题的行为吗? @pilcrow 查看编辑。 strace 返回一个很长的列表,我应该寻找什么?我确信当sleep 开启时输出不是剩余的(我在运行之前删除了目录的全部内容)。我不明白你关于叉子的问题。顺便说一句:github.com/bioperl/bioperl-live/blob/master/scripts/Bio-DB-GFF/… strace -fe trace=process my_perl_script 应该可以帮助您入门。然而,@mobrule 从$? 中发现了这一点。 【参考方案1】:

$? = 13 表示您的子进程被SIGPIPE 信号终止。您的外部程序 (bp_genbank2gff3.pl) 尝试将一些输出写入您的perl 程序的管道。但是perl 程序关闭了管道的末端,因此您的操作系统向外部程序发送了SIGPIPE

sleeping 3 秒,你让你的程序在操作系统杀死它之前运行 3 秒,所以这会让你的程序完成一些事情。请注意,管道的容量有限,所以如果您的父 perl 脚本没有从管道读取,并且如果外部程序正在向标准输出写入大量内容,则外部程序的写入操作最终会阻塞,您可能不会真的从您的外部程序中获得 3 秒的努力。

解决方法是读取外部程序的输出,即使您打算将其丢弃。

open( my $command_out, "-|", $command );
my @ignore_me = <$command_out>;
close $command_out;


更新:如果你真的不关心命令的输出,你可以通过将输出重定向到/dev/null来避免SIGPIPE问题:

open my $command_out, "-|", "$command > /dev/null";
close $command_out;     # succeeds, no SIGPIPE

当然,如果您要费那么大劲来忽略输出,您不妨直接使用system


附加信息:正如 OP 所说,关闭管道文件句柄会导致父级等待子级完成(通过使用 waitpid 或类似的东西)。但是它开始等待之前,它关闭了管道的末端。在这种情况下,该端是子进程正在将其标准输出写入的管道的读取端。下次子进程尝试向标准输出写入内容时,操作系统检测到该管道的读取端已关闭,并向子进程发送SIGPIPE,将其杀死并迅速让父进程中的close 语句完成.

【讨论】:

我什么也不懂。我的外部程序确实尝试编写一些输出(例如开头的“正在处理...”然后结尾的“完成”)。但是为什么你的意思是“perl 程序关闭了它的管道末端”?换句话说,为什么 my @ignore_me = &lt;$command_out&gt;; 会产生如此大的影响? @David B - 当一个进程(在本例中是您的孩子)写入读取器(您的父母)已经关闭的管道时,您会得到一个 SIGPIPE。我在父级中写入&lt;$command_out&gt;,您将保持管道的读取端打开,直到管道的写入端完成。 它会读取孩子正在尝试写的内容。这会有所不同。您正在将水转向花园软管并通过不读取您打开管道的程序的输出来堵塞软管的尖端。如果您不想要程序的输出,请使用system 规则,@Sinan 和 @pilcrow - 谢谢大家。这很有趣。【参考方案2】:

我不确定您要做什么,但 system 在这种情况下可能会更好...

【讨论】:

我使用open 因为我想即时读取命令stdout,据我所知,使用system 无法完成(system 等待命令完成然后立即返回所有输出)。上面的例子只是一个简化版本,不包含这部分,但对问题无关紧要。 好的,所以你需要在打开和关闭之间有一个“while() do stuff ” 这是我正在使用的通用方法。所以有时我确实想用命令发送到标准输出的东西来做点什么,然后我真的按照你的建议使用while。但如果我不这样做,为什么命令不能正确执行?例如,我们在这里讨论的命令获取某种格式的文件名,然后将其数据拆分为两个不同格式的文件。它生成文件并将一些日志消息打印到标准输出。在这种情况下,我不关心标准输出,所以我不使用一段时间。为什么这很重要?

以上是关于perl中管道文件句柄的问题的主要内容,如果未能解决你的问题,请参考以下文章

Perl open3读取gnuplot块的管道句柄

Perl基础命令---文件句柄基础

Perl 中文件句柄的内置名称是啥?

文件句柄怪癖 Perl

如何将 Perl 的 system() 的输出重定向到文件句柄?

Perl文件句柄引用