16

我想创建一个简单的 IO 对象,它代表一个向另一个程序打开的管道,我可以在我的应用程序运行时定期写入另一个程序的 STDIN。我希望它是防弹的(因为它可以捕获所有错误)和跨平台。我能找到的最佳选择是:

open

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

好处:

  • 跨平台
  • 简单的

缺点

  • $SIG{PIPE}从管道程序中捕获错误
  • 是否发现了其他错误?

IO::管道

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

好处:

  • 简单的
  • 为 OO 接口返回一个 IO::Handle 对象
  • 由 Perl 核心支持。

缺点

  • 仍然没有$SIG{PIPE}从管道程序中捕获错误
  • Win32 不支持(或者,至少跳过了它的测试)

IPC::运行

IPC::Run 中没有用于写入文件句柄的接口,仅附加到标量。这似乎……很奇怪。

IPC::Run3

这里也没有文件句柄接口。我可以使用代码引用,它会被重复调用以假脱机给孩子,但查看源代码,它似乎实际上写入了一个临时文件,然后打开它并将内容假脱机到管道命令的STDIN. 世界卫生大会?

IPC::Cmd

仍然没有文件句柄接口。


我在这里想念什么?似乎这应该是一个已解决的问题,但我有点震惊,事实并非如此。IO::Pipe 最接近我想要的,但缺乏$SIG{PIPE}错误处理和缺乏对 Windows 的支持令人苦恼。JDWIM 的管道模块在哪里?

4

2 回答 2

6

感谢@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_appenderhack。
  • 对魔法$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."
于 2012-05-22T05:07:02.540 回答
0

我做过类似的事情。尽管这取决于父程序以及您要通过管道传输的内容。我的解决方案是生成一个子进程(让 $SIG{PIPE} 运行)并将其写入日志,或者以您认为合适的方式处理错误。我使用 POSIX 来处理我的子进程,并且能够利用父进程的所有功能。但是,如果您试图让孩子与父母交流 - 那么事情就会变得困难。你有一个主程序的例子和你想要管道的东西吗?

于 2012-05-13T07:01:31.423 回答