Flush output of child process

蹲街弑〆低调 提交于 2019-12-20 03:27:37

问题


I created a child process via IPC::Open2.
I need to read from the stdout of this child process line by line.
Problem is, as the stdout of the child process is not connected to a terminal, it's fully buffered and I can't read from it until the process terminates.

How can I flush the output of the child process without modifying its code ?


child process code

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

parent process code:

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";
}

When I run the code, it get stucks waiting the output of the child process.
However, if I run it with bc the result is what I expect, I believe bc must manually flush its output

note:
In the child process if I add $| = 1 at the beginning or STDOUT->flush() after printing, the parent process can properly read from it.
However this is an example and I must handle programs that don't manually flush their output.


回答1:


One way is to set up a terminal-like environment for the process, a pseudo-terminal (pty). That is hard to do right and is very system dependent, but IPC::Run has that capability ready for easy use.

Here is the driver, run using at facility so that it has no controlling terminal (or run it via 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

With >pty> it sets up a pseudo-terminal for STDOUT of the program in @cmd (it's a pipe with >); also see <pty< and see more about redirection. The anonymous sub {} gets called every time there is output from the child, so one can process it as it goes. There are other options for this as well.

The program that is called (t_term.pl) only tests for a terminal

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

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

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

The output shows that the called program (t_term.pl) does see a terminal on its STDOUT, even when a driver runs without one (using at, or when run out of a crontab). If the >pty> is changed to the usual redirection with > (using a pipe) then there is no terminal.

Whether this solves the buffering problems is clearly up to that program, and whether it is enough to fool it with a terminal.

Another way around the problem is using unbuffer when possible, as in mob's answer.




回答2:


Unfortunately Perl has no control over the buffering behavior of the programs it executes. Some systems have an unbuffer utility that can do this. If you have access to this tool, you could say

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

There's a discussion here about the equivalent tools for Windows, but I couldn't say whether any of them are effective.



来源:https://stackoverflow.com/questions/54538273/flush-output-of-child-process

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!