简体   繁体   English

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

[英]How can I read the error output of external commands in Perl?

As part of a larger Perl program, I am checking the outputs of diff commands of input files in a folder against reference files, where a blank output (a match) is a passing result, and any output from diff is a fail result. 作为更大的Perl程序的一部分,我正在检查文件夹中输入文件的diff命令的输出与参考文件,其中空白输出(匹配)是传递结果,diff的任何输出都是失败结果。

The issue is, if the target folder is short on the number of expected files, the exception diff throws doesn't come as output, creating false passes. 问题是,如果目标文件夹缺少预期文件的数量,则异常diff throws不会作为输出,从而创建错误传递。

Output Example: 输出示例:

diff: /testfolder/Test-02/test-output.2: No such file or directory

Test-01: PASS 测试01:通过

Test-02: PASS 测试02:通过

The code goes as such: 代码如下:

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

This is a quick hackery job to fail if there is any output from the diff call. 如果diff调用有任何输出,这是一个快速的hackery作业失败。 Is there a way to check for exceptions thrown by the command called in the backticks ? 有没有办法检查backticks调用的命令引发的异常?

There's the answer in perlfaq8: How can I capture STDERR from an external command? perlfaq8中有答案:我如何从外部命令捕获STDERR?


There are three basic ways of running external commands: 运行外部命令有三种基本方法:

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

With system(), both STDOUT and STDERR will go the same place as the script's STDOUT and STDERR, unless the system() command redirects them. 使用system(),STDOUT和STDERR将与脚本的STDOUT和STDERR位于同一位置,除非system()命令重定向它们。 Backticks and open() read only the STDOUT of your command. 反引号和open()只读取命令的STDOUT。

You can also use the open3() function from IPC::Open3. 您还可以使用IPC :: Open3中的open3()函数。 Benjamin Goldberg provides some sample code: Benjamin Goldberg提供了一些示例代码:

To capture a program's STDOUT, but discard its STDERR: 要捕获程序的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);

To capture a program's STDERR, but discard its STDOUT: 要捕获程序的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);

To capture a program's STDERR, and let its STDOUT go to our own STDERR: 要捕获程序的STDERR,让它的STDOUT转到我们自己的STDERR:

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

To read both a command's STDOUT and its STDERR separately, you can redirect them to temp files, let the command run, then read the temp files: 要分别读取命令的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> ) {}

But there's no real need for both to be tempfiles... the following should work just as well, without deadlocking: 但是真的不需要两者都是临时文件......下面的内容也应该同样有效,没有死锁:

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> ) {}

And it'll be faster, too, since we can begin processing the program's stdout immediately, rather than waiting for the program to finish. 而且它也会更快,因为我们可以立即开始处理程序的标准输出,而不是等待程序完成。

With any of these, you can change file descriptors before the call: 使用其中任何一个,您可以在调用之前更改文件描述符:

open(STDOUT, ">logfile");
system("ls");

or you can use Bourne shell file-descriptor redirection: 或者您可以使用Bourne shell文件描述符重定向:

$output = `$cmd 2>some_file`;
open (PIPE, "cmd 2>some_file |");

You can also use file-descriptor redirection to make STDERR a duplicate of STDOUT: 您还可以使用文件描述符重定向使STDERR成为STDOUT的副本:

$output = `$cmd 2>&1`;
open (PIPE, "cmd 2>&1 |");

Note that you cannot simply open STDERR to be a dup of STDOUT in your Perl program and avoid calling the shell to do the redirection. 请注意,您不能简单地在您的Perl程序中打开STDERR作为STDOUT的副本,并避免调用shell来执行重定向。 This doesn't work: 这不起作用:

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

This fails because the open() makes STDERR go to where STDOUT was going at the time of the open(). 这会失败,因为open()使STDERR转到open()时STDOUT的去向。 The backticks then make STDOUT go to a string, but don't change STDERR (which still goes to the old STDOUT). 反引号然后使STDOUT转到字符串,但不要更改STDERR(它仍然转到旧的STDOUT)。

Note that you must use Bourne shell (sh(1)) redirection syntax in backticks, not csh(1)! 请注意,必须在反引号中使用Bourne shell(sh(1))重定向语法,而不是csh(1)! Details on why Perl's system() and backtick and pipe opens all use the Bourne shell are in the versus/csh.whynot article in the "Far More Than You Ever Wanted To Know" collection in http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz . 有关为什么Perl的系统()以及反引号和管道打开全部使用Bourne shell的详细信息,请参阅http://www.cpan.org/中“远远超过您想知道的”中的vs / csh.whynot文章。 misc / olddoc / FMTEYEWTK.tgz To capture a command's STDERR and STDOUT together: 要一起捕获命令的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

To capture a command's STDOUT but discard its STDERR: 捕获命令的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

To capture a command's STDERR but discard its STDOUT: 捕获命令的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

To exchange a command's STDOUT and STDERR in order to capture the STDERR but leave its STDOUT to come out our old STDERR: 要交换命令的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

To read both a command's STDOUT and its STDERR separately, it's easiest to redirect them separately to files, and then read from those files when the program is done: 要分别读取命令的STDOUT和STDERR,最简单的方法是将它们单独重定向到文件,然后在程序完成时从这些文件中读取:

system("program args 1>program.stdout 2>program.stderr");

Ordering is important in all these examples. 在所有这些示例中,排序很重要。 That's because the shell processes file descriptor redirections in strictly left to right order. 那是因为shell以严格从左到右的顺序处理文件描述符重定向。

system("prog args 1>tmpfile 2>&1");
system("prog args 2>&1 1>tmpfile");

The first command sends both standard out and standard error to the temporary file. 第一个命令将标准输出和标准错误发送到临时文件。 The second command sends only the old standard output there, and the old standard error shows up on the old standard out. 第二个命令仅在那里发送旧的标准输出,并且旧的标准错误显示在旧的标准输出上。

Programs themselves can't throw "exceptions", but they can return nonzero error codes. 程序本身不能抛出“异常”,但它们可以返回非零错误代码。 You can check the error code of a program run with backticks or system() in Perl using $?: 您可以使用$?检查在Perl中使用反引号或system()运行的程序的错误代码:

$toPrint = "FAIL" if $?;

(Add this line before the loop that tests @output .) (在测试@output的循环之前添加此行。)

Check perlvar for $? 检查perlvar是否为$? . If it's set to 0, there were no signals, and the return code from the program is also zero. 如果它设置为0,则没有信号,程序的返回码也为零。 That's probably what you want. 这可能就是你想要的。

In this case, you could even just use system and check its return value for being zero, while redirecting the stdout and stderr to /dev/null. 在这种情况下,您甚至可以使用系统并检查其返回值为零,同时将stdout和stderr重定向到/ dev / null。

Assuming that diff errors wind up on STDERR, if you'd like to be able to examine or log the errors, I recommend the CPAN module Capture::Tiny: 假设差异错误在STDERR上结束,如果您希望能够检查或记录错误,我建议使用CPAN模块Capture :: Tiny:

use Capture::Tiny 'capture';
my ($out, $err) = capture { system($command) };

That's like backticks, but gives you STDOUT and STDERR separately. 这就像反叛,但分别给你STDOUT和STDERR。

There is a list of interesting ways to work with the output of a backticks-enclosed command at the perldoc site . 有一系列有趣的方法可以在perldoc站点处使用反引号封装命令的输出。 You'll have to scroll down to or search for "qx/STRING/" (without the quotes) 您必须向下滚动或搜索“qx / STRING /”(不带引号)

You could also make a run through with the output of 'diff -d' which will make your code easier to read. 您还可以使用'diff -d'的输出进行运行,这将使您的代码更易于阅读。

foreach (`diff -d $args`){
  if (/^Only in/){
     do_whatever();
  }
}

你也可以:

my @output = `$command 2>\&1`;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM