3

I am trying to communicate with an interactive process. I want my perl script to be a "moddle man" between the user and the process. The process puts text to stdout, prompts the user for a command, puts more text to stdout, prompts the user for a command, ....... A primitive graphic is provided:

 User <----STDOUT---- interface.pl <-----STDOUT--- Process
 User -----STDIN----> interface.pl ------STDIN---> Process
 User <----STDOUT---- interface.pl <-----STDOUT--- Process
 User -----STDIN----> interface.pl ------STDIN---> Process
 User <----STDOUT---- interface.pl <-----STDOUT--- Process
 User -----STDIN----> interface.pl ------STDIN---> Process

The following simulates what I'm trying to do:

    #!/usr/bin/perl

    use strict;
    use warnings;

    use FileHandle;
    use IPC::Open2;
    my  $pid = open2( \*READER, \*WRITER, "cat -n" );
    WRITER->autoflush(); # default here, actually
    my $got = "";
    my $input = " ";

    while ($input ne "") {
            chomp($input = <STDIN>);
            print WRITER "$input \n";
            $got = <READER>;
            print $got;
    }

DUe to output buffering the above example does not work. No matter what text is typed in, or how many enters are pressed the program just sits there. The way to fix it is to issue:

    my  $pid = open2( \*READER, \*WRITER, "cat -un" );

Notice "cat -un" as opposed to just "cat -n". -u turns off output buffering on cat. When output buffering is turned off this works. The process I am trying to interact with most likely buffers output as I am facing the same issues with "cat -n". Unfortunately I can not turn off output buffering on the process I am communicating with, so how do I handle this issue?

UPDATE1 (using ptty):

    #!/usr/bin/perl

    use strict;
    use warnings;

    use IO::Pty;
    use IPC::Open2;

    my $reader = new IO::Pty;
    my $writer = new IO::Pty;

    my  $pid = open2( $reader, $writer, "cat -n" );
    my $got = "";
    my $input = " ";

    $writer->autoflush(1);

    while ($input ne "") {
            chomp($input = <STDIN>);
            $writer->print("$input \n");
            $got = $reader->getline;
            print $got;
    }

~

4

1 回答 1

6

There are three kinds of buffering:

  1. Block buffering: Output is placed into a fixed-sized buffer. The buffer is flushed when it becomes full. You'll see the output come out in chunks.
  2. Line buffering: Output is placed into a fixed-sized buffer. The buffer is flushed when a newline is added to the buffer and when it becomes full.
  3. No buffering: Output is passed directly to the OS.

In Perl, buffering works as follows:

  • File handles are buffered by default. One exception: STDERR is not buffered by default.
  • Block buffering is used. One exception: STDOUT is line buffered if and only if it's connected to a terminal.
  • Reading from STDIN flushes the buffer for STDOUT.
  • Until recently, Perl used 4KB buffers. Now, the default is 8KB, but that can be changed when Perl is built.

This first two are surprisingly standard across all applications. That means:

  • User -------> interface.pl

    User is a person. He doesn't buffer per say, though it's a very slow source of data. OK

  • interface.pl ----> Process

    interface.pl's output is block buffered. BAD

    Fixed by adding the following to interface.pl:

    use IO::Handle qw( );
    WRITER->autoflush(1);
    
  • Process ----> interface.pl

    Process's output is block buffered. BAD

    Fixed by adding the following to Process:

    use IO::Handle qw( );
    STDOUT->autoflush(1);
    

    Now, you're probably going to tell me you can't change Process. If so, that leaves you three options:

    • Use a command line or configuration option provided by tool to change its buffering behaviour. I don't know of any tools that provide such an option.
    • Fool the child to use line buffering instead of block buffering by using a pseudo tty instead of a pipe.
    • Quitting.

  • interface.pl -------> User

    interface.pl's output is line buffered. OK (right?)

于 2012-09-28T16:43:56.770 回答