感谢@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
hack。
- 对魔法
$in
和$out
标量的写作感觉很奇怪,而且动作很遥远。我不喜欢它。
- 如果标量是文件句柄,则无法对标量进行面向行的处理。因此,它们的效率较低。
- 使用
new_chunker
获取面向行的输出的能力很好,虽然还是有点奇怪。不过,假设它被 IPC::Run 有效缓冲,那么从程序中读取输出的效率会有所提高。
我现在意识到,虽然 IPC::Run 的界面可能会更好一些,但总体而言,IPC 模型的弱点尤其使它难以处理。没有普遍有用的 IPC 接口,因为必须对正在运行的特定程序的细节了解太多才能使其工作。没关系,也许,如果你确切地知道它将如何对输入做出反应,并且能够可靠地识别它何时完成发射输出,并且不需要过多担心跨平台兼容性。但这远远不足以满足我对在 CPAN 模块中与各种数据库命令行客户端交互的普遍有用方法的需求,该模块可以分发到整个操作系统主机。
最后,感谢博文评论中的打包建议,我决定放弃使用 IPC 来控制这些客户端,而改用DBI。它提供了优秀的API,健壮、稳定、成熟,没有IPC的缺点。
我对那些追随我的人的建议是:
- If you just need to execute another program and wait for it to finish, or collect its output when it is done running, use IPC::System::Simple. Otherwise, if what you need to do is to interactively interface with something else, use an API whenever possible. And if it's not possible, then use something like IPC::Run and try to make the best of it—and be prepared to give up quite a bit of your time to get it "just right."