什么是首选的跨平台IPC Perl模块?

dojqjjoe  于 2023-10-24  发布在  Perl
关注(0)|答案(3)|浏览(127)

我想创建一个简单的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::Pipe

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

优点:

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

缺点

  • 仍然没有$SIG{PIPE}来捕获管道程序中的错误
  • Win32不支持(或者至少跳过its tests

IPC::Run

在IPC::Run中没有用于写入文件句柄的接口,只能追加到标量。这很奇怪。

IPC::Run3

这里也没有文件句柄接口。我可以使用一个代码引用,它会被反复调用以spool到子对象,但是查看源代码,它似乎实际上写入了一个临时文件,然后打开它并将 * 其 * 内容spool到管道命令的STDIN

IPC::Cmd

仍然没有文件句柄接口。
我错过了什么?看起来这应该是一个解决的问题,我有点震惊,它不是。IO::Pipe最接近我想要的,但缺乏$SIG{PIPE}错误处理和缺乏对Windows的支持是令人沮丧的。

zqry0prt

zqry0prt1#

感谢@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的写入时不会发出提示。(通过调用echo __END__)。我还在调用new_chunker后使用了一个匿名子例程来将输出收集到一个数组中,而不是标量(如果需要,只需将标量的引用传递给'>')。
所以这是可行的,但在我看来,这有很多原因:

  • 通常没有什么有用的方法来知道一个IPC控制的程序是否完成了打印到它的STDOUT。相反,你必须在它的输出上使用一个正则表达式来搜索一个通常意味着它已经完成的字符串。
  • 如果它不发出一个,你必须欺骗它发出一个(就像我在这里所做的那样--如果我有一个名为__END__的文件,上帝会禁止的)。如果我控制一个数据库客户端,我可能必须发送类似SELECT 'IM OUTTA HERE';的东西。不同的应用程序需要不同的new_appender hack。
  • 写入神奇的$in$out标量感觉很奇怪,而且是远程操作。我不喜欢它。
  • 不能像文件句柄那样对标量进行面向行的处理,因此效率较低。
  • 使用new_chunker获得面向行的输出的能力很好,如果仍然有点奇怪的话。这在从程序阅读输出时恢复了一些效率,假设IPC::Run有效地缓冲了它。

我现在意识到,虽然IPC的接口::Run可能会更好一点,总体而言,IPC模型的弱点尤其使其难以处理。没有通用的IPC接口,因为人们必须知道太多关于正在运行的特定程序的细节才能使其工作。这是好的,* 也许 *,如果你确切地知道它将如何对输入做出React,并且可以可靠地识别何时完成输出,也不需要担心跨平台的兼容性,但这远远不足以满足我对一种通用的方式来与各种数据库命令进行交互的需求-CPAN模块中的线路客户端,可以分布到整个操作系统主机。
最后,感谢a blog post评论中的打包建议,我决定放弃使用IPC来控制这些客户端,而是使用the DBI。它提供了一个优秀的API,健壮,稳定和成熟,并且没有IPC的缺点。
我对那些在我之后的人的建议是:

  • 如果你只需要执行另一个程序并等待它完成,或者在它完成运行时收集它的输出,请使用IPC::System::Simple。否则,如果你需要做的是与其他东西交互,请尽可能使用API。如果不可能,然后使用像IPC::Run这样的东西,并尽量充分利用它,并准备好给予相当多的时间来使它“恰到好处”。
0sgqnhkj

0sgqnhkj2#

我也做过类似的事情。虽然这取决于父程序和你试图传递的东西。我的解决方案是生成一个子进程(保留$SIG{PIPE}运行)并将其写入日志,或者以你认为合适的方式处理错误。我使用POSIX来处理我的子进程,并且能够利用父进程的所有功能。但是,如果你试图让子进程你有一个主程序的例子和你想管道的东西吗?

vdgimpew

vdgimpew3#

可以从(几乎)任意数量的子进程中重定向标准输出和标准错误,并由select()触发异步读取它们。子进程不必像接受的答案那样合作,尽管如果它们使用行缓冲或无缓冲I/O会有所帮助。
对于POSIX/Un*x,您只需使用pipe()fork()POSIX::dup()exec()select()的常规组合,或者您提到的更高级别的模块之一,如IPC::Open3()IPC::Run()等。
所有这些都不能在Windows下工作,因为fork()exec()的Perl模拟不足以完成这项任务,而select()只能在套接字上工作。
对于Windows,你需要做的是:
1.创建一个socketpair()(使用Perl的socketpair()模拟)。
1.使用$true = 1; ioctl $child_stdout, 0x8004667e, \$true在套接字对的读端启用非阻塞I/O。

  1. dup()STDOUT并保存重复的描述符。
  2. dup()套接字对的写入端替换STDOUT
    1.使用Win32::Process::Create()创建子进程,而不是使用fork()exec()
    1.恢复STDOUT
    1.等待子进程用select()sysread()从套接字对的读端写入(分别为flush())到标准输出。
    STDERR的过程是相同的。对于STDIN,你必须交换读端和写端。
    常量0x8004667e在Windows头文件中称为FIONBIO,但在Perl中不可用。
    我已经写了一篇博客文章,里面有更深入的信息:http://www.guido-flohr.net/platform-independent-asynchronous-child-process-ipc/。可以在这里找到一个包含Perl和C语言工作代码的git仓库:https://github.com/gflohr/platform-independent-ipc

相关问题