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

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

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

Пример вывода:

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

Test-01: PASS

Тест-02: PASS

Код выглядит следующим образом:

$command = "(diff call on 2 files)";
my @output = `$command`;
print "Test-02: ";
$toPrint = "PASS";
foreach my $x (@output) {
    if ($x =~ /./) {
        $toPrint = "FAIL";
    }
}

Это быстрое хакерское задание для сбоя, если есть какой-либо вывод из вызова diff. Есть ли способ проверить исключения, вызванные командой, вызванной в backticks?

Ответы

Ответ 1

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

$toPrint = "FAIL" if $?;

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

Ответ 2

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


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

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

С системой() STDOUT и STDERR будут находиться в том же месте, что и script STDOUT и STDERR, если команда system() не перенаправляет их. Backticks и 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)) в backticks, а не csh (1)! Подробная информация о том, почему Perl system() и backtick и pipe открывается, все используют оболочку Bourne в статье versus/csh.whynot в коллекции "Far More Than You Want Want To To Know" в 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");

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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

foreach ('diff -d $args'){
  if (/^Only in/){
     do_whatever();
  }
}

Ответ 7

Вы также можете:

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