如何从Perl中的外部命令中丢弃STDERR?

时间:2010-07-16 10:30:51

标签: perl

我希望在使用自定义错误消息替换其标准错误输出时捕获外部命令的退出代码。

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

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

2 个答案:

答案 0 :(得分: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()

使用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/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

捕获命令的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");

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

答案 1 :(得分: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.
}