如何在Perl中同时捕获stderr,stdout和退出代码?

时间:2020-03-06 14:29:28  来源:igfitidea点击:

是否可以从Perl运行外部进程,捕获其stderr,stdout和进程退出代码?

我似乎能够做到这些的组合,例如使用反引号获取标准输出,使用IPC :: Open3捕获输出,使用system()获取退出代码。

如何一次性捕获stderr,stdout和退出代码?

解决方案

如果我们重新阅读了IPC :: Open3的文档,则会看到一条注释,我们应该调用waitpid来获得子进程。完成此操作后,状态应该在" $?"中可用。退出值为$? >> 8。看
perldoc perlvar中的$。

运行外部命令的三种基本方法:

system $cmd;        # using system()
$output = `$cmd`;       # using backticks (``)
open (PIPE, "cmd |");   # using open()

使用system(),除非system()命令将其重定向,否则STDOUT和STDERR都将与脚本的STDOUT和STDERR放在同一位置。反引号和open()仅读取命令的STDOUT

我们还可以通过open调用类似以下内容的代码,以同时重定向STDOUT和STDERR。

open(PIPE, "cmd 2>&1 |");

如@Michael Carman所述,返回码始终存储在$?中。

(更新:我更新了IO :: CaptureOutput的API,以使其变得更加容易。)

有几种方法可以做到这一点。这是使用IO :: CaptureOutput模块的一个选项:

use IO::CaptureOutput qw/capture_exec/;

my ($stdout, $stderr, $success, $exit_code) = capture_exec( @cmd );

这是capture_exec()函数,但是IO :: CaptureOutput也具有更通用的capture()函数,可用于捕获Perl输出或者来自外部程序的输出。因此,如果某些Perl模块恰巧使用某些外部程序,则仍会得到输出。

这也意味着我们只需要记住一种捕获STDOUT和STDERR(或者合并)的方法,而不是将IPC :: Open3用于外部程序和其他用于捕获Perl输出的模块。

如果我们不想要STDERR的内容,那么IPC :: System :: Simple模块中的capture()命令几乎就是我们想要的:

use IPC::System::Simple qw(capture system $EXITVAL);

   my $output = capture($cmd, @args);

   my $exit_value = $EXITVAL;

我们可以将capture()与单个参数一起使用来调用外壳程序,或者将多个参数与可靠地一起使用来避免外壳程序。还有Capturex(),即使只有一个参数,也永远不会调用Shell。

与Perl的内置system和backticks命令不同,IPC :: System :: Simple在Windows下返回完整的32位退出值。如果命令无法启动,死于信号或者返回意外的退出值,则还会引发详细的异常。这意味着对于许多程序,我们可以依靠而不是自己检查退出值
IPC :: System ::为我们轻松完成工作:

use IPC::System::Simple qw(system capture $EXIT_ANY);

 system( [0,1], "frobincate", @files);     # Must return exitval 0 or 1

 my @lines = capture($EXIT_ANY, "baznicate", @files);  # Any exitval is OK.

 foreach my $record (@lines) {
     system( [0, 32], "barnicate", $record);  # Must return exitval 0 or 32
 }

IPC :: System :: Simple是纯Perl,没有依赖项,可在Unix和Windows系统上使用。不幸的是,它没有提供捕获STDERR的方法,因此它可能不适合所有需求。

IPC :: Run3提供了一个简洁易用的界面来重新连接所有三个通用文件句柄,但是不幸的是,它不检查命令是否成功,因此我们需要检查$?手动,这一点都不有趣。提供检查$?的公共接口自检查$以来,我的IPC :: System :: Simple任务清单上有什么?跨平台的方式并不是我希望任何人完成的任务。

IPC ::名称空间中还有其他模块,它们也可以为我们提供帮助。 YMMV。

一切顺利,

保罗

如果我们变得非常复杂,则可能需要尝试Expect.pm。但是,如果我们也不需要管理将输入发送到流程,那可能就太过分了。