16

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

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

输出示例:

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

测试 01:通过

测试 02:通过

代码如下:

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

如果调用有任何输出,这是一项快速的黑客工作diff。有没有办法检查调用的命令引发的异常backticks

4

7 回答 7

31

perlfaq8中有答案:How can I capture STDERR from an external command?


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

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

使用 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);

要捕获程序的 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);

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

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

要分别读取命令的 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> ) {}

但是两者都不是临时文件的真正需要......以下应该也可以工作,没有死锁:

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

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

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

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

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

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

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

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

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

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

这失败了,因为 open() 使 STDERR 转到 open() 时 STDOUT 所在的位置。然后反引号使 STDOUT 转到一个字符串,但不要更改 STDERR(它仍然转到旧的 STDOUT)。

请注意,您必须在反引号中使用 Bourne shell (sh(1)) 重定向语法,而不是 csh(1)!关于为什么 Perl 的 system() 和反引号和管道打开都使用 Bourne shell 的详细信息,请参见 http://www.cpan.org/的“远超你想知道”集合中的 vs/csh.whynot 文章杂项/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

要捕获命令的 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

要捕获命令的 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

交换命令的 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

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

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

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

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

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

于 2009-04-22T19:30:09.827 回答
17

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

$toPrint = "FAIL" if $?;

(在测试循环之前添加这一行@output。)

于 2009-04-22T14:43:30.450 回答
5

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

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

于 2009-04-22T14:42:53.637 回答
4

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

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

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

于 2009-04-22T16:13:29.690 回答
1

在perldoc 站点上有一系列有趣的方法来处理反引号括起来的命令的输出。您必须向下滚动或搜索“qx/STRING/”(不带引号)

于 2009-04-22T14:50:27.033 回答
0

您还可以使用 'diff -d' 的输出进行运行,这将使您的代码更易于阅读。

foreach (`diff -d $args`){
  if (/^Only in/){
     do_whatever();
  }
}
于 2009-04-22T15:20:35.900 回答
0

你也可以:

my @output = `$command 2>\&1`;
于 2009-04-22T15:23:49.467 回答