Получение статуса выхода из STDIN в Perl - PullRequest
2 голосов
/ 08 июня 2010

У меня есть Perl-скрипт, который запускается с такой командой:

/path/to/binary/executable | /path/to/perl/script.pl

Сценарий выполняет полезные действия для вывода двоичного файла, а затем завершается, когда заканчивается STDIN (<> возвращает undef). Это все хорошо, кроме случаев, когда двоичный файл выходит с ненулевым кодом. Из сценария POV он думает, что сценарий только что завершился чисто, и поэтому он очищает и завершает работу с кодом 0 .

Есть ли способ для сценария perl увидеть, что был за код выхода? В идеале я бы хотел, чтобы что-то вроде этого работало:

# close STDIN, and if there was an error, exit with that same error.
unless (close STDIN) {
   print "error closing STDIN: $! ($?)\n";
   exit $?;
}

Но, к сожалению, это не работает:

$ (date; sleep 3; date; exit 1) | /path/to/perl/script.pl /tmp/test.out
Mon Jun  7 14:43:49 PDT 2010
Mon Jun  7 14:43:52 PDT 2010
$ echo $?
0

Есть ли способ заставить его делать то, что я имею в виду?

Отредактировано, чтобы добавить:

Скрипт perl управляет выводом двоичной команды в режиме реального времени, поэтому буферизация всего этого в файл не является возможным решением. Однако ему не нужно знать код завершения до конца скрипта.

Ответы [ 5 ]

8 голосов
/ 08 июня 2010

Переменная окружения bash $PIPESTATUS - это массив, содержащий состояния каждой части конвейера последней команды.Например:

$ false | true; echo "PIPESTATUS: ${PIPESTATUS[@]};  ?: $?"
PIPESTATUS: 1 0;  ?: 0

Таким образом, вместо рефакторинга вашего сценария Perl вам просто нужен сценарий, запускающий команду piped для проверки $PIPESTATUS.Использование $PIPESTATUS без [@] дает значение первого элемента массива.

Если вам нужно проверить состояние как исходного исполняемого файла, так и сценария perl, вы хотите назначить $ PIPESTATUS болеесначала к другой переменной:

status=(${PIPESTATUS[@]})

Затем вы можете проверить их по отдельности, например,

if (( ${status[0]} )); then echo "main reactor core breach!"; exit 1;
elif (( ${status[1]} )); then echo "perls poisoned by toxic spill!"; exit 2;
fi;

Вы должны сделать это с помощью временной переменной, потому что следующий оператор, даже если это *Оператор 1016 * сбросит ${PIPESTATUS[@]} до того, как следующий оператор, даже если это оператор elif, сможет его проверить.

Обратите внимание, что этот материал работает только с bash, а не с исходной оболочкой bourne (обычно sh, хотя многие системы связывают /bin/sh с /bin/bash из-за его обратной совместимости).Таким образом, если вы поместите это в сценарий оболочки, первая строка должна быть

#!/bin/bash

, а не #!/bin/sh.

4 голосов
/ 08 июня 2010

Для уточнения предложения Ether, это обходной подход оболочки:

bash-2.03$ TF=/tmp/rc_$$; (/bin/false; echo $?>$TF) | 
           perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0 
OUT
bash-2.03$ echo $?
1
bash-2.03$ TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | 
           perl5.8 -e 'print "OUT\n"'; test `cat $TF|tr -d "\012"` -eq 0     
OUT
bash-2.03$ echo $?
0
bash-2.03$ 

Минусы:

  • Общее уродство

  • Оставляет беспорядок / tmp / rc_ * файлов вокруг

  • Потеря точного значения ненулевого кода выхода (который в приведенном выше примере был 255)

Вы можете справиться со всеми этими недостатками, незначительно отредактировав свой Perl-скрипт:

  • Прочитать содержимое файла с именем $ENV{TF} (используя File::Slurp::read_file()), скажем, в my $rc
  • chomp $rc;
  • unlinking $ENV{TF}
  • exit $rc
  • Тогда ваша командная строка становится: TF=/tmp/rc_$$; (/bin/true; echo $?>$TF) | /your/perl/script

Это немного менее инвазивное изменение по сравнению с изменением сценария Ether для использования вызова system() - вы просто добавляете 4 строки в самый конец сценария (включая exit); но как только вы меняете сценарий, я бы, вероятно, порекомендовал сделать все возможное и внести изменения, предложенные Эфиром.

3 голосов
/ 08 июня 2010

вижу два варианта:

  • вы можете переписать скрипт так, чтобы он вызывал саму команду, чтобы он мог определить свой статус выхода и предпринять другие действия, если он не завершился успешно
  • Вы можете заключить вызов команды в сценарий shell , который проверил значение выхода и затем вызвал Perl-скрипт по-другому (по сути, то же самое, что и опция 1, за исключением того, что он не требует изменения Perl скрипт).

Однако, поскольку вы читаете ввод в вашем Perl-скрипте из команды до его выхода, очевидно, у вас еще нет кода возврата. Вы получите доступ к этому только после завершения команды, поэтому вам нужно будет буферизовать ее вывод где-то еще, например, в файле:

use IPC::System::Simple qw(system $EXITVAL);
use File::Temp;

my $tempfile = File::Temp->new->filename;
system("/path/to/binary/executable > $tempfile");
if ($EXITVAL == 0)
{
     system("/path/to/perl/script.pl < $tempfile");
}
else
{
     die "oh noes!";
}
2 голосов
/ 08 июня 2010

Вы получаете статус выхода только для своих собственных дочерних процессов.То, что связано с вашим STDIN, не является дочерним процессом Perl;это оболочка.К сожалению, то, что вы хотите, не возможно.

1 голос
/ 08 июня 2010

К сожалению, bash выбрасывает статус выхода на конвейере. Запуск «sleep 3 | echo hi» инициирует эхо еще до того, как сон завершится, поэтому у него нет абсолютно никакой возможности зафиксировать состояние выхода первой команды.

Вы можете (теоретически) выполнить это, изменив команду bash на список команд - bash сохранит значение в $? (точно так же, как Perl), но затем вы должны каким-то образом передать его скрипту Perl, что означает, что вашему скрипту Perl нужно будет принять состояние выхода предыдущей программы, скажем, из командной строки.

В качестве альтернативы, вы можете просто переписать сценарий Perl для запуска команды и зафиксировать состояние выхода или обернуть все это в еще один сценарий.

...