Perl Advent Calendar 2006-12-14

Three I/O Channels, Too Many Options, & a Command to Run with IPC

by Josh McAdams & Jerrad Pierce

Yesterday's review of Shell covered a simple but elegant way to integrate shell commands into your Perl programs. Alas, Shell only allows you to capture STDOUT of child processes. What do you do when you need more control?

Perl staples such as system, qx, and open allow some degree of control over what is fed into your program, but not enough over the results. system only returns success or failure and inherits (partial) I/O from your program. qx returns captured STDOUT and sets $?. open provides a little more control with the ability to open pipes, and dup descriptors. But still, all of these methods can be brittle, are fraught with complications, and make your code less portable across platforms. If you want to capture error messages you have to perform shell-specific tricks like redirecting STDERR to STDOUT i.e; 2>&1

There have been many attempts to make interacting with child processes easier. The core distribution even includes modules such as IPC::Open2 and IPC::Open3. However, the one module that seems to be relatively light-weight, Perlish, and portable1 is IPC::Run3.

IPC::Run3 is capable of feeding a running subprocess's STDIN while seperately capturing its STDOUT and STDERR. The module exports a single subroutine, run3. This subroutine accepts a command list followed by expressions for handling input, output, and error. These expressions can be undefined, file handles, file names, or references to scalars. If the value is undefined, the child inherits the file descriptor from the parent process. If it is defined, then the specified file or expression is used for I/O, unless it is a reference to undef in which case the corresponding descriptor is opened to the local equivalent of /dev/null

The example below simply puts some data into a scalar variable and then calls perl itself with that data, splitting it into standard output and standard error portions. As you can see, IPC::Run3 does what you would think that is should. It reads and writes data on the specified channels. The run3 subroutine returns true for success and dies on failure. In addition, sub-program exit code is stored in $?. Just about the only thing to look out for is the error check after eval. Be sure to check $? a.k.a. $CHILD_ERROR for the process exit code.

mod14.pl


   1 use strict;
   2 use warnings;
   3 use IPC::Run3;
   4 
   5 my($in, $out, $err) = ("merry\nxmas\n");
   6 my @cmd =
   7   (q{perl -e 'print scalar <STDIN>; print STDERR scalar <STDIN>; exit 42;'});
   8 
   9 eval {
  10   run3(@cmd, \$in, \$out, \$err);
  11 };
  12 
  13 if($@) {
  14   die "Something bad happened: $@\n";
  15 }
  16 
  17 if($?) {
  18   printf "Exit code indicates problems: %i\n", $?>>8;
  19 }
  20 
  21 s/\n/\\n/g foreach($in, $out, $err);
  22 print "IN: $in\nOUT: $out\nERR: $err\n";
1. With the limitation that concurrent children running via IPC::Run3 may not be threadsafe, YMMV.