是否可以从 Perl 运行外部进程,捕获其标准错误、标准输出和进程退出代码?
我似乎能够将这些组合起来,例如使用反引号来获取标准输出,使用 IPC::Open3 来捕获输出,以及使用 system() 来获取退出代码。
如何一次捕获标准错误、标准输出和退出代码?
(更新:我更新了 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 输出。
如果您重新阅读 IPC::Open3 的文档,您会看到一条说明,您应该调用waitpid来获取子进程。完成此操作后,状态应该在$?
. 退出值为$? >> 8
。请参阅
$?
perldoc perlvar。
如果您不想要 STDERR 的内容,那么IPC::System::Simple模块中的 capture() 命令几乎正是您所追求的:
use IPC::System::Simple qw(capture system $EXITVAL);
my $output = capture($cmd, @args);
my $exit_value = $EXITVAL;
您可以使用带有单个参数的 capture() 来调用 shell,或者使用多个参数来可靠地避免 shell。还有一个从不调用 shell 的 capturex(),即使只有一个参数。
与 Perl 的内置系统和反引号命令不同,IPC::System::Simple 在 Windows 下返回完整的 32 位退出值。如果命令无法启动、死于信号或返回意外的退出值,它也会引发详细的异常。这意味着对于许多程序,您可以依靠 IPC::System::Simple 为您完成艰苦的工作,而不是自己检查退出值:
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::命名空间中的其他模块也可以为您提供帮助。YMMV。
祝一切顺利,
保罗
运行外部命令的三种基本方式:
system $cmd; # using system()
$output = `$cmd`; # using backticks (``)
open (PIPE, "cmd |"); # using open()
使用system()
,STDOUT
和 STDERR 将与脚本的位置相同STDOUT
,STDERR,
除非system()
命令重定向它们。反引号并open()
只读STDOUT
您的命令。
您还可以使用 open 调用类似以下内容来重定向STDOUT
和STDERR
。
open(PIPE, "cmd 2>&1 |");
$?
如@Michael Carman所述,返回码始终存储在其中。
如果你变得非常复杂,你可能想试试 Expect.pm。但是,如果您也不需要管理向进程发送输入,那可能就有点过头了。
我发现IPC:run3非常有帮助。您可以将所有子管道转发到一个 glob 或一个变量;非常简单地!退出代码将存储在 $? 中。
下面是我如何抓取我知道会是一个数字的 stderr。cmd 将信息转换输出到标准输出(我使用 > 将其通过管道传输到 args 中的文件),并报告了对 STDERR 的转换次数。
use IPC::Run3
my $number;
my $run = run3("cmd arg1 arg2 >output_file",\undef, \undef, \$number);
die "Command failed: $!" unless ($run && $? == 0);