Проблема с файловым дескриптором в Perl

Я пытаюсь запустить bp_genbank2gff3.pl (пакет bioperl) из другого сценария perl, который получает в качестве аргумента genbank.

Это не работает (выходные файлы не создаются):

   my $command = "bp_genbank2gff3.pl -y -o /tmp $ARGV[0]";

   open( my $command_out, "-|", $command );
   close $command_out;

но это делает

   open( my $command_out, "-|", $command );
   sleep 3; # why do I need to sleep?
   close $command_out;

Почему?

Я думал, что close должен блокироваться до тех пор, пока команда не будет выполнена:

Закрытие любого переданного дескриптора файла приводит к тому, что родительский процесс ожидает завершения дочернего процесса... (см. http://perldoc.perl.org/functions/open.html).

Изменить

Я добавил это как последнюю строку:

say "ret=$ret, \$?=$?, \$!=$!";

и в обоих случаях распечатка:

ret=, $?=13, $!=

(что означает, что close потерпел неудачу в обоих случаях, верно?)


person David B    schedule 13.08.2010    source источник
comment
Каково возвращаемое значение close() ? Что такое $? и, возможно, $!? bp_genbank2gff3.pl или расширение оболочки $ARGV[0] разветвляются и выходят? Что, по словам strace или truss, происходит? Вы уверены, что выходные файлы в рабочем случае не остались от несвязанной, успешной работы? Можете ли вы воспроизвести проблемное поведение с помощью какой-нибудь общедоступной утилиты оболочки, а не bp_...3.pl ?   -  person pilcrow    schedule 13.08.2010
comment
@pilcrow см. редактирование. strace возвращает очень длинный список, что мне искать? Я уверен, что вывод не является остатком, когда sleep включен (я удаляю все содержимое каталога перед запуском). Я не понял вашего вопроса о вилке. Кстати: github.com/bioperl/ bioperl-live/blob/master/scripts/Bio-DB-GFF/   -  person David B    schedule 13.08.2010
comment
strace -fe trace=process my_perl_script должно помочь вам начать. Однако @mobrule понял это из $?.   -  person pilcrow    schedule 13.08.2010


Ответы (2)


$? = 13 означает, что ваш дочерний процесс был прерван сигналом SIGPIPE. Ваша внешняя программа (bp_genbank2gff3.pl) пыталась записать некоторый вывод в канал для вашей программы perl. Но программа perl закрыла свой конец канала, поэтому ваша ОС отправила SIGPIPE внешней программе.

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

Обходной путь — прочитать вывод внешней программы, даже если вы просто собираетесь его выбросить.

open( my $command_out, "-|", $command );
my @ignore_me = <$command_out>;
close $command_out;


Обновление: если вас действительно не волнует вывод команды, вы можете избежать проблем SIGPIPE, перенаправив вывод на /dev/null:

open my $command_out, "-|", "$command > /dev/null";
close $command_out;     # succeeds, no SIGPIPE

Конечно, если вы собираетесь приложить столько усилий, чтобы проигнорировать вывод, вы можете просто использовать system.


Дополнительная информация: как говорит OP, закрытие переданного дескриптора файла заставляет родителя ждать завершения дочернего элемента (с помощью waitpid или чего-то подобного). Но прежде чем он начнет ждать, он закрывает свой конец канала. В данном случае этот конец является концом канала для чтения, в который дочерний процесс записывает свой стандартный вывод. В следующий раз, когда дочерний процесс попытается записать что-то в стандартный вывод, ОС обнаружит, что конец этого канала для чтения закрыт, и отправит SIGPIPE дочернему процессу, уничтожив его и быстро позволив оператору close в родительском процессе завершиться.

person mob    schedule 13.08.2010
comment
Я что-то не понимаю. Моя внешняя программа действительно пыталась записать некоторый вывод (что-то вроде работы над... в начале, а затем в конце). Но почему вы подразумеваете, что Perl-программа закрыла свой конец канала? Другими словами, почему именно my @ignore_me = <$command_out>; это имеет такое значение? - person David B; 13.08.2010
comment
@David B - вы получаете SIGPIPE, когда один процесс (в данном примере ваш ребенок) записывает в канал, который читатель (ваш родитель) уже закрыл. Когда я пишу <$command_out> в родительском элементе, вы держите конец канала для чтения открытым до тех пор, пока не закончится конец канала для записи. - person mob; 13.08.2010
comment
Он читает то, что ребенок пытается написать. Это имеет значение. Вы направляете воду в садовый шланг и затыкаете кончик шланга, не читая вывод программы, которой открыли трубу. Если вам не нужен вывод программы, используйте system. - person Sinan Ünür; 13.08.2010
comment
правило, @Sinan и @pilcrow - всем спасибо. Это было интересно. - person David B; 13.08.2010

Я не уверен, что вы пытаетесь сделать, но system, вероятно, лучше в Это дело...

person sebthebert    schedule 13.08.2010
comment
Я использую open, так как я хотел бы прочитать команду stdout на лету, что нельзя сделать с помощью system, насколько мне известно (system ждет завершения команды, а затем возвращает все выходные данные сразу). Приведенный выше пример — это просто упрощенная версия, которая не включает эту часть, но она не имеет значения для проблемы. - person David B; 13.08.2010
comment
Итак, вам нужно некоторое время (‹$command_out›) { сделать что-то } между открытием и закрытием - person sebthebert; 13.08.2010
comment
Это общий метод, который я использую. Поэтому иногда я действительно хочу что-то сделать с тем, что команда отправляет на стандартный вывод, и тогда я действительно использую while, как вы предложили. Но если я этого не сделаю, почему команда не должна выполняться правильно? Команда, которую мы здесь обсуждаем, например, получает имя файла некоторого формата, а затем разбивает его данные на два файла разных форматов. Он генерирует файлы, а также выводит некоторые сообщения журнала на стандартный вывод. В этом случае меня не волнует стандартный вывод, поэтому я не использую while. Почему это должно иметь значение? - person David B; 13.08.2010