2010-07-16 55 views
2

我想捕获外部命令的退出代码,同时用自定义错误消息替换其标准错误输出。如何从Perl的外部命令中丢弃STDERR?

my $ret = system("which mysql"); 
if ($ret != 0) { 
    say "Error"; 
} 

如果mysql可执行文件是不存在的,它显示which命令错误信息,我不想要。如何摆脱它?

+0

相关:http://stackoverflow.com/q/8733131/2157640 – Palec 2014-09-12 15:23:30

回答

10

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() 

随着系统(),标准输出和STDERR会去同一个地方为脚本的STDOUT和STDERR,除非系统()命令重定向他们。反引号和open()只读取命令的标准输出。 您也可以使用IPC :: Open3中的open3()函数。本杰明·戈德堡提供了一些示例代码: 为了捕捉一个程序的标准输出,而放弃其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); 

为了捕捉一个程序的标准错误,但放弃其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); 

为了捕捉一个程序的标准错误,并让其STDOUT去我们自己的STDERR:

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

要单独阅读这两个命令的STDOUT和STDERR,您可以将其重定向到临时文件,L等运行的命令,然后读取临时文件:

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

而且它也会更快,因为我们可以立即开始处理程序的stdout,而不是等待程序完成。 与任何这些,你可以在呼叫前更改文件描述符:

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 |"); 

请注意,你不能简单地将STDERR开成STDOUT的复制在你的Perl程序,避免调用壳里做的REDI rection。这不起作用:

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

失败的原因是开放的()让STDERR转至STDOUT打算在开放时间()。反引号会使STDOUT转到一个字符串,但不要更改STDERR(它仍然会转到旧的STDOUT)。 请注意,您必须在反引号中使用Bourne shell(sh(1))重定向语法,而不是csh(1)!关于为什么Perl的system()以及反向和管道打开都使用Bourne shell的详细信息,请参见http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz中“远远超过你想知道的”集合中的vs/csh.whynot文章。要捕获一个命令的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 

要捕获一个命令的标准输出,但是却丢弃其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 

要捕获一个命令的标准错误,但是却丢弃其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"); 

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

+0

$输出='CMD 2>的/ dev/null';这只是答案。 – Tree 2010-07-16 10:46:46

+0

不,还有另外一个答案,就是所有的第一个例子。 – 2010-07-16 10:51:43

+0

不,重定向技巧是*不是唯一的答案。在两种情况下,它是错误的*答案:其中一个命令包含shell元字符,并且您希望完全避免该shell(例如,通过将列表提供给系统或打开);或者如果该命令包含来自不受信任来源的东西(shell注入,任何人?) – trent 2017-01-25 16:07:05

2

一个更现代的Perl方法可能是使用一个模块,如Capture::Tiny。例如:

#!/usr/bin/perl 
use strict; 
use warnings; 

use Capture::Tiny qw/capture/; 

my $ret; 

# you may ignore stdout/stderr, if you wish, by commenting out the next line 
my ($stdout, $stderr) = 
    capture { 
    $ret = system("which mysql"); 
    }; 

if ($ret!=0) { 
    print " error " ; 
    # perhaps even something cool like 
    # print $stderr if $verbose # where $verbose is set by a command-line flag. 
}