Какой кросс-платформенный модуль IPC Perl предпочтительнее?

Я хочу создать простой объект ввода-вывода, который представляет собой канал, открытый для другой программы, в который я могу периодически записывать в STDIN другой программы во время работы моего приложения. Я хочу, чтобы он был пуленепробиваемым (то есть ловил все ошибки) и кроссплатформенным. Лучшие варианты, которые я могу найти, это:

open

sub io_read {
    local $SIG{__WARN__} = sub { }; # Silence warning.
    open my $pipe, '|-', @_ or die "Cannot exec $_[0]: $!\n";
    return $pipe;
}

Преимущества:

  • Кроссплатформенность
  • Простой

Недостатки

  • Нет $SIG{PIPE} для перехвата ошибок из конвейерной программы
  • Обнаружены ли другие ошибки?

В/В:: Труба

sub io_read {
    IO::Pipe->reader(@_);
}

Преимущества:

  • Простой
  • Возвращает объект IO::Handle для объектно-ориентированного интерфейса.
  • Поддерживается ядром Perl.

Недостатки

  • По-прежнему нет $SIG{PIPE} для обнаружения ошибок из конвейерной программы
  • Не поддерживается в Win32 (или, по крайней мере, его тесты пропущено)

МПК::Выполнить

В IPC::Run нет интерфейса для записи в дескриптор файла, только добавление к скаляру. Это кажется… странным.

МПК:: Run3

Здесь также нет интерфейса файлового дескриптора. Я мог бы использовать ссылку на код, которая будет многократно вызываться для буферизации дочернего элемента, но, глядя на исходный код, кажется, что он на самом деле записывает во временный файл, а затем открывает его и буферизует его. содержимое в файл STDIN команды pipe. Что?

IPC::Cmd

По-прежнему нет интерфейса файлового дескриптора.


Что мне здесь не хватает? Кажется, что это должна быть решенная проблема, и я немного ошеломлен, что это не так. IO::Pipe ближе всего подходит к тому, что я хочу, но отсутствие обработки ошибок $SIG{PIPE} и отсутствие поддержки Windows огорчают. Где модуль трубопровода, который будет использовать JDWIM?


person theory    schedule 13.05.2012    source источник
comment
Вы ошибаетесь насчет IPC::Run. Он может обрабатывать файловые дескрипторы без проблем. Он может даже делать сумасшедшие перенаправления и псевдотерминалы.   -  person ikegami    schedule 13.05.2012
comment
IPC::Run3 также может обрабатывать дескрипторы файлов. Откуда вы берете информацию?   -  person ikegami    schedule 13.05.2012
comment
используйте sigtrap для сигналов канала.   -  person daxim    schedule 13.05.2012
comment
@ikegami — из документации. Теперь я вижу дескриптор файла в IPC::Run. Не знаю, как я пропустил это раньше, за исключением того, что я уделял больше внимания IPC::Run3, где я могу передать дескриптор файла, но печать в него, казалось, игнорировалась после запуска команды.   -  person theory    schedule 14.05.2012
comment
@daxim — sigtrap выглядит полезным, но мне действительно нужен модуль, в котором мне не нужно об этом думать, где ошибки просто превращаются в исключения, например, что IPC::System::Simple делает для system и обратных галочек.   -  person theory    schedule 14.05.2012
comment
Э, позвольте мне перефразировать. Раньше я замечал поддержку дескриптора файла в IPC::Run, и теперь я вспомнил проблему: похоже, я могу открыть какой-то другой файл для ЧТЕНИЯ, и он будет помещен в STDIN дочернего элемента. Что мне нужно, так это дескриптор файла, в который я могу ЗАПИСАТЬ, когда моя программа работает, и он будет буферизирован дочернему элементу, когда я пишу. Вот как работает дескриптор файла open |-.   -  person theory    schedule 14.05.2012
comment
Что мне нужно, так это дескриптор файла, в который я могу ЗАПИСАТЬ: Так что используйте канал. Как ты сказал, что хотел сделать. Есть даже встроенная поддержка создания этих каналов для вас. Это прямо в синопсисе!!!   -  person ikegami    schedule 14.05.2012
comment
Обратите внимание, что выполнение IPC с файловыми дескрипторами очень сложно, если у вас более одного файлового дескриптора (select!!). Так что, хотя IPC::Run дает вам возможность сделать это, это должно быть последним средством. Плюс IPC::Run по сравнению с другими, которые вы упомянули, заключается в том, что он может скрыть от вас каналы.   -  person ikegami    schedule 14.05.2012
comment
О, смотри, мини-язык для труб. Я полностью пропустил это. (Мои глаза затуманились после прочтения синопсиса ранее.) Я запустил модуль, который Я пофантазировал, но перед тем, как продолжить, я внимательно прочитаю IPC::Run (во всяком случае, пока он работает не совсем правильно…).   -  person theory    schedule 15.05.2012
comment
@ikegami: Итак, я вижу, как использовать скалярные буферы ссылок, что немного странно, но приятно (вероятно, я могу абстрагировать их в объектно-ориентированный интерфейс, поскольку в противном случае мне понадобилась бы куча атрибутов в моем классе для отслеживания жгут и буфера). Я действительно хотел бы, чтобы он был специально ориентирован на строки, по крайней мере, для чтения вывода. Вот почему мне нравились дескрипторы файлов: я могу использовать <$fh> или $fh->getline для перебора строк…   -  person theory    schedule 15.05.2012
comment
@Theory, нет, вы не можете безопасно использовать <$fh> aka $fh->getline, если у вас более одной трубы. Это может привести к тупику. Выполнение IPC с файловыми дескрипторами действительно сложно, если у вас более одного файлового дескриптора (select!!).   -  person ikegami    schedule 15.05.2012
comment
Ага. Современного искусства здесь, к сожалению, не хватает, я нашел. Я решил отказаться от использования IPC в этом проекте. Спасибо за помощь. О, и @ikegami, если вы хотите оставить ответ о IPC::Run (наименее плохой вариант?), Я был бы рад принять его. В противном случае я добавлю комментарий bitchfest и приму его.   -  person theory    schedule 16.05.2012


Ответы (2)


Благодаря рекомендациям @ikegami я обнаружил, что лучшим выбором для интерактивного чтения и записи в другой процесс в Perl является IPC::Run. Однако для этого требуется, чтобы программа, из которой вы читаете и пишете, имела известный вывод, когда она завершает запись в свой STDOUT, например приглашение. Вот пример, который выполняет bash, запускает ls -l, а затем печатает этот вывод:

use v5.14;
use IPC::Run qw(start timeout new_appender new_chunker);

my @command = qw(bash);

# Connect to the other program.
my ($in, @out);
my $ipc = start \@command,
    '<' => new_appender("echo __END__\n"), \$in,
    '>' => new_chunker, sub { push @out, @_ },
    timeout(10) or die "Error: $?\n";

# Send it a command and wait until it has received it.
$in .= "ls -l\n";
$ipc->pump while length $in;

# Wait until our end-of-output string appears.
$ipc->pump until @out && @out[-1] =~ /__END__\n/m;

pop @out;
say @out;

Поскольку он работает как IPC (я предполагаю), bash не выдает подсказку, когда завершает запись в свой STDOUT. Поэтому я использую функцию new_appender(), чтобы она выдавала что-то, что я могу сопоставить, чтобы найти конец вывода (путем вызова echo __END__). Я также использовал анонимную подпрограмму после вызова new_chunker для сбора вывода в массив, а не в скаляр (просто передайте ссылку на скаляр в '>', если хотите).

Так что это работает, но, на мой взгляд, это отстой по целому ряду причин:

  • Как правило, нет полезного способа узнать, что программа, управляемая IPC, закончила печать на свой STDOUT. Вместо этого вы должны использовать регулярное выражение на его выходе для поиска строки, которая обычно означает, что это сделано.
  • Если он не выдает его, вы должны заставить его выдать один (как я сделал здесь — не дай бог, если бы у меня был файл с именем __END__). Если бы я управлял клиентом базы данных, мне, возможно, пришлось бы отправить что-то вроде SELECT 'IM OUTTA HERE';. Для разных приложений потребуются разные new_appender приемы.
  • Запись магических скаляров $in и $out кажется странной и похожей на действие на расстоянии. Мне это не нравится.
  • Нельзя выполнять линейно-ориентированную обработку скаляров, как если бы они были дескрипторами файлов. Поэтому они менее эффективны.
  • Возможность использовать new_chunker для получения линейного вывода хороша, хотя и немного странна. Тем не менее, это восстанавливает некоторую эффективность при чтении вывода программы, если предположить, что он эффективно буферизуется IPC::Run.

Теперь я понимаю, что, хотя интерфейс для IPC::Run потенциально мог бы быть немного лучше, общие недостатки модели IPC, в частности, делают ее сложной для решения. Не существует универсально полезного интерфейса IPC, потому что нужно слишком много знать о специфике конкретной выполняемой программы, чтобы заставить ее работать. Это нормально, возможно, если вы точно знаете, как он будет реагировать на входные данные, и сможете надежно распознать, когда он закончит выдачу вывода, и вам не нужно сильно беспокоиться о межплатформенной совместимости. Но этого было далеко недостаточно для моей потребности в общем полезном способе взаимодействия с различными клиентами командной строки базы данных в модуле CPAN, который можно было бы распространять на целый ряд операционных систем.

Наконец, благодаря предложениям по упаковке в комментариях к сообщению в блоге я решил отказаться от использования IPC для управления этими клиентами и вместо этого использовать DBI. Он предоставляет отличный API, надежный, стабильный и зрелый, и не страдает ни одним из недостатков IPC.

Моя рекомендация для тех, кто придет после меня, такова:

  • Если вам просто нужно выполнить другую программу и дождаться ее завершения или собрать ее выходные данные после завершения ее работы, используйте IPC::System::Simple. В противном случае, если вам нужно интерактивно взаимодействовать с чем-то еще, по возможности используйте API. А если это невозможно, то используйте что-то вроде IPC::Run и попытайтесь сделать так, чтобы лучшее из этого — и будьте готовы потратить немало времени, чтобы сделать это «как раз правильно».
person theory    schedule 22.05.2012

Я сделал что-то похожее на это. Хотя это зависит от родительской программы и того, что вы пытаетесь передать. Мое решение состояло в том, чтобы создать дочерний процесс (оставив $SIG{PIPE} функционировать) и записать его в журнал или обработать ошибку так, как вы считаете нужным. Я использую POSIX для обработки своего дочернего процесса и могу использовать все функции родителя. Однако, если вы пытаетесь заставить ребенка общаться с родителем, тогда все становится сложно. У вас есть пример основной программы и что вы пытаетесь PIPE?

person Levi Beers    schedule 13.05.2012
comment
В основном я хочу знать, выходит ли ребенок, и чтобы эта смерть распространялась на родителя, чтобы я мог справиться с этой ситуацией. Основная программа – Sqitch, а дочерние – psql, mysql, sqlite3 и т. п. - person theory; 14.05.2012