IPC::Open3 quirks

polettix on 2007-03-30T23:23:45

I sometimes record some modules as possible "future save-the-day solutions" when I read about them, mostly on Perl Monks. One of these modules is IPC::Open3, but I'd take more care in ensuring these safe nets.

IPC::Open3 is useful when you want complete input/output control on a spawned process, i.e. when you have to control its STDIN, STDOUT and STDERR as well. The interface is quite straightforward:

$pid = open3(\*WTRFH, \*RDRFH, \*ERRFH,
             'some cmd and args', 'optarg', ...);

my($wtr, $rdr, $err);
$pid = open3($wtr, $rdr, $err,
             'some cmd and args', 'optarg', ...);

One thing that the docs state quite clearly is the following:

If ERRFH is false, or the same file descriptor as RDRFH, then STDOUT and STDERR of the child are on the same filehandle.

It's easy (at least for me) to overlook this, and you can imagine my surprise discovering that all the STDERR of the spawned process goes straight to STDOUT, and the filehandle for $err is undefined. What's not so easy is to discover how one can address this issue. IMHO, the "Do What I Mean" mantra has been spectacularly betrayed here, where I would expect a brand new handle to be created for me. Just like $wtr and $rdr, you know. Just like the filehandle in open().

Thanks to this thread, I discovered one solution to the problem: gensym. Here's a working snippet, at last:

#!/usr/bin/perl
# see also http://www.perlmonks.org/?node_id=150748

use strict;
use warnings;
use IPC::Open3 qw( open3 );
use Symbol;    # provides gensym

my ($in, $out, $err);
$err = gensym();  # Aha! This was what I was after
my $pid = open3($in, $out, $err, '/bin/ls', '/path/to/inexistent', '/tmp')
  or die "open3(): $!";

my @emsg = <$err>; # The error comes from here
print {*STDOUT} "process STDERR: @emsg\n" if @emsg;

my @omsg = <$out>; # Output for existing stuff
print {*STDOUT} "process STDOUT @omsg\n" if @omsg;

__END__

poletti@PolettiX:~/sviluppo/perl$ perl open3-gensym.pl
process STDERR: /bin/ls: /path/to/inexistent: No such file or directory

process STDOUT /tmp:
 prova
 v206821
 v206828
 v274678
 v279868
 v283182

Something to remember well for the future...