如何在Perl中读取外部命令的错误输出?

ale*_*ood 15 perl backticks

作为更大的Perl程序的一部分,我正在检查diff文件夹中输入文件的命令输出与参考文件,其中空白输出(匹配)是传递结果,diff的任何输出都是失败结果.

问题是,如果目标文件夹缺少预期文件的数量,则异常diff throws不会作为输出,从而创建错误传递.

输出示例:

diff: /testfolder/Test-02/test-output.2: No such file or directory
Run Code Online (Sandbox Code Playgroud)

测试01:通过

测试02:通过

代码如下:

$command = "(diff call on 2 files)";
my @output = `$command`;
print "Test-02: ";
$toPrint = "PASS";
foreach my $x (@output) {
    if ($x =~ /./) {
        $toPrint = "FAIL";
    }
}
Run Code Online (Sandbox Code Playgroud)

如果diff呼叫有任何输出,这是一个快速的hackery作业失败.有没有办法检查被调用的命令抛出的异常backticks

bri*_*foy 28

perlfaq8中有答案:我如何从外部命令捕获STDERR?


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

system $cmd;        # using system()
$output = `$cmd`;       # using backticks (``)
open (PIPE, "cmd |");   # using open()
Run Code Online (Sandbox Code Playgroud)

使用system(),STDOUT和STDERR将与脚本的STDOUT和STDERR位于同一位置,除非system()命令重定向它们.反引号和open()只读取命令的STDOUT.

您还可以使用IPC :: Open3中的open3()函数.Benjamin Goldberg提供了一些示例代码:

要捕获程序的STDOUT,但丢弃其STDERR:

use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open(NULL, ">", File::Spec->devnull);
my $pid = open3(gensym, \*PH, ">&NULL", "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Run Code Online (Sandbox Code Playgroud)

要捕获程序的STDERR,但丢弃其STDOUT:

use IPC::Open3;
use File::Spec;
use Symbol qw(gensym);
open(NULL, ">", File::Spec->devnull);
my $pid = open3(gensym, ">&NULL", \*PH, "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Run Code Online (Sandbox Code Playgroud)

要捕获程序的STDERR,让它的STDOUT转到我们自己的STDERR:

use IPC::Open3;
use Symbol qw(gensym);
my $pid = open3(gensym, ">&STDERR", \*PH, "cmd");
while( <PH> ) { }
waitpid($pid, 0);
Run Code Online (Sandbox Code Playgroud)

要分别读取命令的STDOUT和STDERR,可以将它们重定向到临时文件,让命令运行,然后读取临时文件:

use IPC::Open3;
use Symbol qw(gensym);
use IO::File;
local *CATCHOUT = IO::File->new_tmpfile;
local *CATCHERR = IO::File->new_tmpfile;
my $pid = open3(gensym, ">&CATCHOUT", ">&CATCHERR", "cmd");
waitpid($pid, 0);
seek $_, 0, 0 for \*CATCHOUT, \*CATCHERR;
while( <CATCHOUT> ) {}
while( <CATCHERR> ) {}
Run Code Online (Sandbox Code Playgroud)

但是真的不需要两者都是临时文件......下面的内容也应该同样有效,没有死锁:

use IPC::Open3;
use Symbol qw(gensym);
use IO::File;
local *CATCHERR = IO::File->new_tmpfile;
my $pid = open3(gensym, \*CATCHOUT, ">&CATCHERR", "cmd");
while( <CATCHOUT> ) {}
waitpid($pid, 0);
seek CATCHERR, 0, 0;
while( <CATCHERR> ) {}
Run Code Online (Sandbox Code Playgroud)

而且它也会更快,因为我们可以立即开始处理程序的标准输出,而不是等待程序完成.

使用其中任何一个,您可以在调用之前更改文件描述符:

open(STDOUT, ">logfile");
system("ls");
Run Code Online (Sandbox Code Playgroud)

或者您可以使用Bourne shell文件描述符重定向:

$output = `$cmd 2>some_file`;
open (PIPE, "cmd 2>some_file |");
Run Code Online (Sandbox Code Playgroud)

您还可以使用文件描述符重定向使STDERR成为STDOUT的副本:

$output = `$cmd 2>&1`;
open (PIPE, "cmd 2>&1 |");
Run Code Online (Sandbox Code Playgroud)

请注意,您不能简单地在您的Perl程序中打开STDERR作为STDOUT的副本,并避免调用shell来执行重定向.这不起作用:

open(STDERR, ">&STDOUT");
$alloutput = `cmd args`;  # stderr still escapes
Run Code Online (Sandbox Code Playgroud)

这会失败,因为open()使STDERR转到open()时STDOUT的去向.反引号然后使STDOUT转到字符串,但不要更改STDERR(它仍然转到旧的STDOUT).

请注意,必须在反引号中使用Bourne shell(sh(1))重定向语法,而不是csh(1)!有关为什么Perl的系统()以及反引号和管道打开全部使用Bourne shell的详细信息,请参阅http://www.cpan.org/中"远比您想知道的更多知识"中的vs/csh.whynot文章. misc/olddoc/FMTEYEWTK.tgz.要一起捕获命令的STDERR和STDOUT:

$output = `cmd 2>&1`;                       # either with backticks
$pid = open(PH, "cmd 2>&1 |");              # or with an open pipe
while (<PH>) { }                            #    plus a read
Run Code Online (Sandbox Code Playgroud)

捕获命令的STDOUT但丢弃其STDERR:

$output = `cmd 2>/dev/null`;                # either with backticks
$pid = open(PH, "cmd 2>/dev/null |");       # or with an open pipe
while (<PH>) { }                            #    plus a read
Run Code Online (Sandbox Code Playgroud)

捕获命令的STDERR但丢弃其STDOUT:

$output = `cmd 2>&1 1>/dev/null`;           # either with backticks
$pid = open(PH, "cmd 2>&1 1>/dev/null |");  # or with an open pipe
while (<PH>) { }                            #    plus a read
Run Code Online (Sandbox Code Playgroud)

要交换命令的STDOUT和STDERR以捕获STDERR,但让它的STDOUT出来我们的旧STDERR:

$output = `cmd 3>&1 1>&2 2>&3 3>&-`;        # either with backticks
$pid = open(PH, "cmd 3>&1 1>&2 2>&3 3>&-|");# or with an open pipe
while (<PH>) { }                            #    plus a read
Run Code Online (Sandbox Code Playgroud)

要分别读取命令的STDOUT和STDERR,最简单的方法是将它们单独重定向到文件,然后在程序完成时从这些文件中读取:

system("program args 1>program.stdout 2>program.stderr");
Run Code Online (Sandbox Code Playgroud)

在所有这些示例中,排序很重要.那是因为shell以严格从左到右的顺序处理文件描述符重定向.

system("prog args 1>tmpfile 2>&1");
system("prog args 2>&1 1>tmpfile");
Run Code Online (Sandbox Code Playgroud)

第一个命令将标准输出和标准错误发送到临时文件.第二个命令仅在那里发送旧的标准输出,并且旧的标准错误显示在旧的标准输出上.


j_r*_*ker 15

程序本身不能抛出"异常",但它们可以返回非零错误代码.您可以system()使用$ ?: 检查使用反引号或Perl 运行的程序的错误代码:

$toPrint = "FAIL" if $?;
Run Code Online (Sandbox Code Playgroud)

(在测试的循环之前添加此行@output.)


xdg*_*xdg 6

假设 diff 错误最终出现在 STDERR 上,如果您希望能够检查或记录错误,我推荐使用 CPAN 模块 Capture::Tiny:

use Capture::Tiny 'capture';
my ($out, $err) = capture { system($command) };
Run Code Online (Sandbox Code Playgroud)

这就像反引号,但分别为您提供 STDOUT 和 STDERR。


Tan*_*lus 5

检查perlvar$? . 如果设置为 0,则没有信号,程序的返回码也为零。这可能就是你想要的。

在这种情况下,您甚至可以只使用system并检查其返回值是否为零,同时将 stdout 和 stderr 重定向到 /dev/null。