Ответ 1
Благодаря руководству от @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
кажется странным, а action-at-a-distance-y. Мне это не нравится. - Нельзя выполнять линейно-ориентированную обработку на скалярах, как если бы они были файловыми дескрипторами. Поэтому они менее эффективны.
- Возможность использовать
new_chunker
для получения вывода, ориентированного на линию, хороша, если все еще немного странно. Это немного повышает эффективность чтения данных из программы, но при условии, что он эффективно буферизуется с помощью IPC:: Run.
Теперь я понимаю, что хотя интерфейс для IPC:: Run потенциально может быть немного приятнее, в целом недостатки модели IPC, в частности, с трудом справляются. В целом нет полезного интерфейса IPC, потому что нужно слишком много знать о специфике конкретной программы, которая запускается, чтобы заставить ее работать. Это нормально, может быть, если вы точно знаете, как он будет реагировать на входные данные, и может надежно распознать, когда это делается с выходом на выходе, и не нужно много беспокоиться о совместимости между платформами. Но этого было недостаточно для моей потребности в общедоступном способе взаимодействия с различными клиентами командной строки базы данных в модуле CPAN, который может быть распространен на весь хост операционных систем.
В конце концов, благодаря предложениям по упаковке в комментариях к сообщению в блоге, я решил отказаться от использования IPC для управления этими клиентами, и вместо этого использовать DBI. Он обеспечивает превосходный API, надежный, стабильный и зрелый и не содержит ни одного из недостатков IPC.
Моя рекомендация для тех, кто идет за мной, это:
- Если вам просто нужно выполнить другую программу и дождаться ее завершения или собрать ее, когда она будет запущена, используйте IPC:: System:: Простой. В противном случае, если вам нужно взаимодействовать с чем-то другим, используйте API, когда это возможно. И если это невозможно, используйте что-то вроде IPC:: Run и постарайтесь сделать все возможное - и будьте готовы отказаться от довольно бит вашего времени, чтобы получить его "в самый раз".