Как я могу прочитать вывод ошибок внешних команд в Perl?

в рамках большей программы Perl я проверяю выходы diff команды входных файлов в папке против ссылочных файлов, где пустой вывод (совпадение) является результатом передачи, а любой вывод из diff является результатом сбоя.

проблема в том, что если целевая папка коротка по количеству ожидаемых файлов, исключение diff не выводится в качестве вывода, создавая ложные проходы.

Выход Пример:

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

7 ответов


сами программы не могут выбрасывать "исключения", но они могут возвращать ненулевые коды ошибок. Вы можете проверить код ошибки программы, запущенной с помощью backticks или system() в Perl с помощью $?:

$toPrint = "FAIL" if $?;

(добавьте эту строку перед циклом, что тесты @output.)


вот ответ в perlfaq8: как я могу захватить STDERR из внешней команды?


существует три основных способа выполнения внешних команд:

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

С system() оба STDOUT и STDERR будут идти в том же месте, что и STDOUT и STDERR скрипта, если команда system () не перенаправит их. Backticks и open () читают только STDOUT вашей команды.

вы также можете использовать функцию open3 () из IPC:: 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 отдельно, вы можете перенаправить их во временные файлы, запустить команду, а затем прочитать temp файлы:

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, чтобы быть dup STDOUT в вашей программе Perl и избежать вызова оболочки для перенаправления. Это не работает:

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

это не удается, потому что open() заставляет STDERR идти туда, где STDOUT шел во время open(). Затем backticks заставляют STDOUT перейти к строке, но не изменяются STDERR (который все еще идет к старому STDOUT).

обратите внимание, что вы должны использовать синтаксис перенаправления Bourne shell (sh(1)) в backticks, а не csh(1)! Подробности о том, почему система Perl() и 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");

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


Регистрация perlvar на $?. Если он установлен в 0, сигналов не было, а код возврата из программы также равен нулю. Наверное, ты этого хочешь.

в этом случае вы можете даже просто использовать система и проверьте его возвращаемое значение на ноль, перенаправляя stdout и stderr в /dev/null.


предполагая, что ошибки diff заканчиваются на STDERR, если вы хотите изучить или зарегистрировать ошибки, я рекомендую модуль CPAN Capture:: Tiny:

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

это похоже на backticks, но дает вам STDOUT и STDERR отдельно.


существует список интересных способов работы с выводом команды backticks-enclosed в сайт perldoc. Вам придется прокручивать вниз или искать "qx / STRING/" (без кавычек)


вы также можете выполнить прогон с выводом "diff-d", который облегчит чтение вашего кода.

foreach (diff -d $args){ если (/^только в/){ делать все(); } }


вы можете:

my @output = `$command 2>\&1`;