Как отказаться от STDERR от внешней команды в Perl? - PullRequest
2 голосов
/ 16 июля 2010

Я хочу записать код выхода внешней команды при замене стандартного вывода ошибки на пользовательское сообщение об ошибке.

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

Если исполняемого файла mysql нет, отображается ошибка команды whichсообщение, которое я не хочу.Как от этого избавиться?

Ответы [ 2 ]

10 голосов
/ 16 июля 2010

См. 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 вашей команды. Вы также можете использовать функцию open3 () из IPC :: 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> ) {}

Но нет никакой необходимости, чтобы оба были tempfiles ... следующее должно работать точно так же, без взаимоблокировки:

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:

$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-программе и избегать вызова оболочки для выполнения перенаправления. Это не работает:

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

Это не удалось, потому что open () заставляет STDERR идти туда, куда STDOUT шел во время open (). Затем обратные метки заставляют STDOUT переходить к строке, но не изменяют STDERR (который все еще идет к старому STDOUT). Обратите внимание, что вы должны использовать синтаксис перенаправления оболочки Bourne (sh (1)) в обратных галочках, а не csh (1)! Подробная информация о том, почему Perl system () и backtick и pipe открывают все, использующие оболочку Bourne, находится в статье versus / csh.whynot в коллекции «Гораздо больше, чем вы хотели знать» в 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");

Заказ важен во всех этих примерах. Это связано с тем, что оболочка обрабатывает перенаправления файловых дескрипторов строго в порядке слева направо.

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

Первая команда отправляет как стандартную ошибку, так и стандартную ошибку во временный файл. Вторая команда отправляет туда только старый стандартный вывод, и старая стандартная ошибка обнаруживается при выходе старого стандарта.

2 голосов
/ 13 июня 2011

Более современным методом 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.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...