amw1 has asked for the wisdom of the Perl Monks concerning the following question:

I ran across the following code the other day. The implementation definately has, some, well issues. However the behavior was a little odd (not the exact code, distilled to highlight the issue):
sub run_cmd { my $cmd = shift(); my $in; my $out; my $err; my $pid = open3($in, $out, $err, $cmd); return ($in, $out, $err); } my ($in, $out, $err) = run_cmd("some command"); while(<$out>) { do stuff with $out; } while(<$err>) { do stuff with $err; }
The problem is that $out gets both STDERR and STDOUT of "some command".

After digging through the perldoc of Open3 and seeing the $dad_err ||= $dad_rdr line in IPC/Open3 the problem was made clear. I re-factored the code to work a bit more like Re: IPC::Open3 woes this so the code works now.

My question is, why is this desired behavior? If I wanted STDERR and STDOUT to be on the same handle I would call open 3 with the same handle for outfh and errfh right? (i.e. open3($in, $out, $out, ...) )

Replies are listed 'Best First'.
Re: open3 question
by graff (Chancellor) on Feb 10, 2005 at 04:37 UTC
    The perldoc man page for IPC::Open3 is a bit quizical on this point; first it says "If ERRFH is false, or the same file descriptor as RDRFH, then STDOUT and STDERR of the child are on the same filehandle." Later it says "If either reader or writer is the null string, this will be replaced by an autogenerated filehandle."

    Okay, so in your case, $in and $out are "null strings", hence they get replaced by autogenerated filehandles. Then, $err happens to be false (same thing as a "null string", you might say, but that's not what matters -- at the point where you pass it to open3, its value is false), hence it becomes a dup of the child's STDOUT.

    I hadn't played with open3() much before now, so it took me a few tries to get a working demo, but here it is. First, a simple, dumb child script that writes to STDOUT and STDERR:

    #!/usr/bin/perl use strict; use IO::Handle; autoflush STDOUT 1; # had to do this, even tho Open3 doc said it woul +d be done already while (<>) { chomp; print "line $. to STDOUT: $_\n"; warn "mesg $. to STDERR\n"; }
    Then, a simple, dumb parent to run the child:
    #!/usr/bin/perl use strict; use IPC::Open3; my $cmd = "test-child.pl"; my ( $in, $out, $err ); $err = "true"; # any string will do (don't use a number) my $pid = open3( $in, $out, $err, $cmd ); my ( $outlog, $errlog ); for ( 1..3 ) { print $in "data $_\n"; $outlog .= <$out>; $errlog .= <$err>; } close $in; waitpid $pid, 0; print "outlog contained:\n$outlog\n"; print "errlog contained:\n$errlog\n"; __OUTPUT__ outlog contained: line 1 to STDOUT: data 1 line 2 to STDOUT: data 2 line 3 to STDOUT: data 3 errlog contained: mesg 1 to STDERR mesg 2 to STDERR mesg 3 to STDERR
    Anyway, to answer your question: I suppose the "if ERRFH is false, set it equal to RDRFH" behavior makes the "simplest default" case come out as "STDERR eq STDOUT" because that's the way shells usually work when returning process output to an interactive user: unless the user specifies otherwise in an interactive shell, stdout and stderr are both presented to the screen.

    Okay, as a rationale, that probably makes no sense in the non-interactive context of a perl script. Oh well...

    (update: to clarify about setting autoflush in the child, that actually makes sense: open3() can only control the properties of filehandles in the parent, and one of its big caveats is that it only works properly when the child is itself configured so as not to buffer its output. I could have done "$| = 1" in the child, instead of using IO::Handle's autoflush call, and it would work just as well.)

      As I said in the OP I got it working, I understand what the behavior is. What I don't understand is the design decision to make it work that way. Was just trying to figure out if there was a reason I'm not seeing.
Re: open3 question
by zentara (Cardinal) on Feb 10, 2005 at 13:56 UTC
    In the code for IPC::Open3, if you set the third (stderr) filehandle to 0, stderr will be sent to stdout
    #my $pid = open3(\*WRITE, \*READ, \*ERROR,"bc"); my $pid = open3(\*WRITE, \*READ,0,"bc"); #if \*ERROR is false, STDERR is sent to STDOUT
    Also in your code you have 2 while loops, one for $out and one for $err. You will be better off using IO::Select to read the filehandles, otherwise the first while loop will "block" unless it's properly constructed.

    I'm not really a human, but I play one on earth. flash japh