我通过IPC::Open2
创建了一个子进程。
我需要逐行读取这个子进程的标准输出。
问题是,由于子进程的stdout没有连接到终端,所以它是完全缓冲的,在进程终止之前我无法读取它。
如何在不修改代码的情况下刷新子进程的输出?
子进程代码
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()
,则父进程可以正确地从中读取。
然而,这是一个示例,我必须处理不手动刷新其输出的程序。
一种方法是为进程建立类似终端的环境,即伪终端(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>
,它为STDOUT
中的@cmd
设置了一个伪终端(它是>
的管道);另见<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)是一种检查终端的合适方法。有关更多/其他方式,请参阅this post。
输出显示被调用程序(t_term.pl
)确实在其STDOUT
上看到一个终端,即使驱动程序没有运行(使用at
,或者当用完crontab
时)。如果使用>pty>
(使用管道)将>
更改为通常的重定向,则没有终端。
这是否解决了缓冲问题显然取决于该程序,以及它是否足以用终端来欺骗它。
绕过这个问题的另一种方法是尽可能使用unbuffer
,就像在mob的回答中一样。