in reply to open3 and IO::Select

None of the things I suggested earlier would fix the problem you mentioned. After some debugging, I found that open3 defaults to using the same pipe for the child's STDOUT and STDERR. (WTF?!? I'd use open2 if I wanted to do that.)

One way around that is to vivify the handles yourself. In the tested code below, that's done using

use Symbol qw( gensym ); my ($to_child, $fr_child, $fr_child_err) = map gensym, 1..3;

And here's the code I used for testing.

#!/usr/bin/perl use strict; use warnings; use IO::Select qw( ); use IPC::Open3 qw( open3 ); use Symbol qw( gensym ); my @cmd = ( 'perl', '-e', <<__EOI__ use IO::Handle qw( ); # Usually want autoflush=1 for pipes. STDOUT->autoflush(0); STDERR->autoflush(0); print STDERR ('a') for 1..10000; STDERR->flush(); print STDOUT ('b') for 1..10000; STDOUT->flush(); print STDERR ('c') for 1..10000; STDERR->flush(); print STDOUT ('d') for 1..10000; STDOUT->flush(); __EOI__ ); { my ($to_child, $fr_child, $fr_child_err) = map gensym, 1..3; my $pid = open3($to_child, $fr_child, $fr_child_err, @cmd); my $sel = IO::Select->new(); $sel->add($fr_child); $sel->add($fr_child_err); while (my @ready = $sel->can_read()) { foreach my $handle (@ready) { if ($handle == $fr_child) { my $bytes_read = sysread($handle, my $buf='', 1024); if ($bytes_read == -1) { warn("Error reading from child's STDOUT: $!\n"); $sel->remove($handle); next; } if ($bytes_read == 0) { print("Child's STDOUT closed\n"); $sel->remove($handle); next; } printf("%4d bytes read from child's STDOUT\n", $bytes_read +); } elsif ($handle == $fr_child_err) { my $bytes_read = sysread($handle, my $buf='', 1024); if ($bytes_read == -1) { warn("Error reading from child's STDERR: $!\n"); $sel->remove($handle); next; } if ($bytes_read == 0) { print("Child's STDERR closed\n"); $sel->remove($handle); next; } printf("%4d bytes read from child's STDERR\n", $bytes_read +); } } # For demonstration purposes only. # Should cause some STDOUT and STDERR reads to become interlaced use Time::HiRes qw( sleep ); sleep(0.1); } for (;;) { my $pid = wait(); last if $pid == -1; # Check the error code of the child process if desired. } }

Replies are listed 'Best First'.
Re^2: open3 and IO::Select
by ddunham (Initiate) on Nov 20, 2007 at 22:47 UTC
    After some debugging, I found that open3 defaults to using the same pipe for the child's STDOUT and STDERR.
    Yes. It's easy to skip over that fact, but it is in the documentation:
           Extremely similar to open2(), open3() spawns the given $cmd and con-
           nects RDRFH for reading, WTRFH for writing, and ERRFH for errors.  If
           ERRFH is false, or the same file descriptor as RDRFH, then STDOUT and
           STDERR of the child are on the same filehandle.
    So the only filehandle that needs to be directly set is ERRFH. You can leave the other two alone.
    (WTF?!? I'd use open2 if I wanted to do that.)
    Not in the same way. You might use a shell to redirect STDERR to STDOUT and then read that new (combined) stream with open2, but you cannot use open2 to read STDERR directly. That's a different behavior from open3 making both streams available on the same perl filehandle (without needing an intervening shell for the redirection).
    --
    Darren

      I realise I come very late in this exchange, but just in case people are still looking for answers, I got it to work with cygwin perl on Windows XP with the following tweaks :

      1/
      use IO::Select; use IO::Socket; use IO::Handle; use IPC::Open3;
      2/
      $Pin = new IO::Handle; $Pin->fdopen(10, "w"); $Pout = new IO::Handle; $Pout->fdopen(11, "r"); $Perr = new IO::Handle; $Perr->fdopen(12, "r"); $Proc = open3($Pin, $Pout, $Perr, $cmdline);
      3/
      $select->add($Pin); $select->add($Pout); $select->add($Perr);

      For $cmdline I used bash, to test things by hand (with STDIN added to my select and printing on $Pin what I get on my perl process' stdin).

      It works beautifully.