See http://perldoc.perl.org/perlfaq8.html#How-can-I-capture-STDERR-from-an-external-command%3f http://perldoc.perl.org/perlfaq8.html#How-can-I-capture-STDERR-from-an-external-command%3f
如何从外部命令捕获 STDERR?
运行外部命令有三种基本方法:
system $cmd; # using system()
$output = `$cmd`; # using backticks (``)
open (PIPE, "cmd |"); # using open()
使用 system() 时,STDOUT 和 STDERR 都将转到与脚本的 STDOUT 和 STDERR 相同的位置,除非 system() 命令重定向它们。反引号和 open() 仅读取命令的 STDOUT。
您还可以使用 IPC::Open3 中的 open3() 函数。本杰明·戈德堡提供了一些示例代码:
要捕获程序的 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 的详细信息,请参阅“远超您想要知道的”集合中的 vs/csh.whynot 文章http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz http://www.cpan.org/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
要捕获命令的 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");
第一个命令将标准输出和标准错误发送到临时文件。第二个命令仅发送旧的标准输出,并且旧的标准错误显示在旧的标准输出上。