刷新子进程的输出

Flush output of child process

我通过 IPC::Open2 创建了一个子进程。
我需要逐行读取这个子进程的标准输出。
问题是,由于子进程的标准输出没有连接到终端,它是完全缓冲的,在进程终止之前我无法读取它。

如何在不修改子进程代码的情况下刷新子进程的输出?


子进程代码

while (<STDIN>) {
    print "Received : $_";
}

父进程代码:

use IPC::Open2;
use Symbol;

my $in = gensym();
my $out = gensym();

my $pid = open2($out, $in, './child_process');

while (<STDIN>) {
    print $in $_;
    my $line = <$out>;
    print "child said : $line";
}

当我 运行 代码时,它卡住等待子进程的输出
但是,如果我 运行 它与 bc 结果是我所期望的,我相信 bc 必须手动刷新它的输出

注意:
在子进程中,如果我在开头添加 $| = 1 或在打印后添加 STDOUT->flush() ,父进程可以正确读取它。
然而,这是一个示例,我必须处理不手动刷新其输出的程序。

不幸的是,Perl 无法控制它执行的程序的缓冲行为。有些系统有一个 unbuffer 实用程序可以执行此操作。如果您有权使用此工具,您可以说

my $pid = open2($out, $in, 'unbuffer ./child_process');

有一个关于 Windows 的等效工具的讨论 here,但我不能说它们是否有效。

(尝试)处理缓冲的一种方法是为进程设置类似终端的环境,即伪终端 (pty)。这通常不容易做到,但 IPC::Run 具有易于使用的能力。

这是驱动程序,运行 用于使用 at 设施进行测试,因此它没有控制终端(或 运行 它通过 cron

use warnings;
use strict;
use feature 'say';

use IPC::Run qw(run);
    
my @cmd = qw(./t_term.pl input arguments); 
    
run \@cmd, '>pty>', sub { say "out: @_" };

#run \@cmd, '>', sub { say "out: @_" }   # no pty

使用>pty>,它为@cmd中程序的STDOUT设置了一个伪终端(这是一个带有>的管道);另见 <pty< 和更多 about redirection。 每次子项有输出时都会调用匿名 sub {} ,因此可以随时处理它。还有其他选项。

被调用的程序(t_term.pl)只测试一个终端

use warnings;
use strict;
use feature 'say';

say "Is STDOUT filehandle attached to a terminal: ",
    ( (-t STDOUT) ? "yes" : "no" );
sleep 2;
say "bye from $$";

-t STDOUT(参见filetest operators) is a suitable way to check for a terminal in this example. For more/other ways see

输出显示被调用程序 (t_term.pl) 在其 STDOUT 上确实看到了一个终端,即使驱动程序 运行 没有终端(使用 at , 或出 crontab)。如果 >pty> 更改为通常的重定向 >(管道),则没有终端。

这是否解决了缓冲问题显然取决于该程序,以及是否足以用终端来愚弄它。

解决该问题的另一种方法是尽可能使用 unbuffer,如暴徒的回答。