2010-06-07 3 views
2

У меня есть Perl-скрипт, который запускается с помощью команды, как это:Захватив статус выхода из STDIN в 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 

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

Edited добавить:

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

ответ

1

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

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

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

+3

«bash выбрасывает статус выхода на труба ": это неправда - см. мой ответ. – intuited

2

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

3

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

  • вы могли бы переписать сценарий так, что он вызывает саму команду, так что он может обнаружить его статус выхода и принимать различные меры, если он не выходил успешно
  • вы может обернуть вызов команды в сценарий 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!"; 
} 
+0

+1 для Perl fix, и я также добавил ответ, в котором излагалось чистое решение оболочки и комбинация решения оболочки с немного меньшим «резким» изменением сценария :) – DVK

4

Выработать по предложению ЭФИР, это является оболочкой обходной подход:

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)

Вы можете иметь дело со всеми этими недостатков путем minorly редактирования сценария Perl для:

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

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

+0

Спасибо - дополнительные недостатки (и в этом случае важны) заключается в том, что выход не будет доступен во время выполнения команды, только после его завершения. – zigdon

+0

@zigdon - извините, не поймал последнее редактирование. Вы правы - вызов дочерней оболочки (скобки) действительно буферизирует вывод комманды, насколько я знаю. – DVK

8

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

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

Так это звучит, как вместо рефакторинга вашего PERL скрипта, вам просто нужно работает скрипт, который водопроводная команду, чтобы проверить $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; 

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

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

#!/bin/bash 

, а не #!/bin/sh.

+0

технически вы должны отправить эти 'echo' es в stderr, используя' echo 'whatever "> & 2;' – intuited

+0

К сожалению, я пытаюсь избежать BASHisms (пытаясь заставить это работать на тире по причинам, не зависящим от моего контроля). В bash я в настоящее время использую 'set -o pipefail', который заставляет весь конвейер возвращать RC первой неудачной команды. – zigdon

+0

Хм. Я думаю, вам нужно использовать временный файл, а затем, как и ответ на @ DVK, но просто прочитайте содержимое временного файла изнутри perl после того, как вы получите EOF из stdin. Я думал об использовании fifo вместо этого, но это не сработает, потому что perl не увидит EOF от stdin до тех пор, пока не будет прочитан fifo, и, по-видимому, не будет проверять fifo до тех пор, пока не будет выполнен вход, т.е. * deadlock *. Хотя я предполагаю, что вы можете затушить тайм-ауты от stdin и fifo до тех пор, пока не воспользуетесь fifo (а затем получите EOF от stdin). Это немного сложно, и вам все равно придется делать fifo в любом случае, но это менее беспорядочно, чем временные файлы. – intuited

Смежные вопросы