################################################################# # # IPC::Open3::Simple - A simple alternative to IPC::Open3 # # $Id: Simple.pm,v 1.7 2006/07/20 13:30:02 erwan Exp $ # # 060714 erwan Created # ################################################################# use strict; use warnings; package IPC::Open3::Simple; use Carp qw(croak confess); use IPC::Open3; use IO::Select; use IO::Handle; use Data::Dumper; our $VERSION = '0.04'; #----------------------------------------------------------------- # # new - constructor. takes a hash where keys are in, out and err # and values are closures/coderefs # sub new { my($pkg,%args) = @_; $pkg = ref $pkg || $pkg; my $self = bless({},$pkg); foreach my $type ('in','out','err') { if (exists $args{$type}) { croak "".__PACKAGE__."::new expects coderefs" if (ref $args{$type} ne 'CODE'); $self->{$type} = $args{$type}; } } return $self; } #----------------------------------------------------------------- # # run - execute a list of shell commands in a separate process # and redirect input/output to the closures provided to new() # sub run { my($self,@args) = @_; # note: in theory, it should work to write: # my $pid = open3($child_in, $child_out, $child_err, @arguments) # but that does not work (bug?). $child_err is then undefined # (in perldoc for open2, the explanation is that stderr=stdout if $child_out == $child_err, which they do when they are both undefined) # TODO: support interactive ipc with child process? my $pid = open3(\*CHILD_IN, \*CHILD_OUT, \*CHILD_ERR, @args) or confess "ERROR: failed to execute command [".join(" ",@args)."]"; my $reader = IO::Select->new(); my $child_in = \*CHILD_IN; my $child_out = \*CHILD_OUT; my $child_err = \*CHILD_ERR; # $child_in->autoflush; IPC::Open3 does it already $child_out->autoflush; $child_err->autoflush; # listen to stdout and stderr, or close them if (exists $self->{out}) { $reader->add($child_out); } else { $child_out->close(); } if (exists $self->{err}) { $reader->add($child_err); } else { $child_err->close(); } # forward stdin to provided function, or close it if (exists $self->{in}) { &{$self->{in}}($child_in); } else { $child_in->close(); } # parse output of cvs command if ($reader->handles) { while (my @ready = $reader->can_read()) { foreach my $fh (@ready) { my $line = <$fh>; if (!defined $line) { # reached EOF on this filehandle $reader->remove($fh); $fh->close(); } else { chomp $line; if ($child_out->opened && fileno($fh) == fileno(\*CHILD_OUT)) { &{$self->{out}}($line); } elsif ($child_err->opened && fileno($fh) == fileno(\*CHILD_ERR)) { &{$self->{err}}($line); } else { confess "BUG: got an unexpected filehandle:".Dumper($fh); } } } } } # wait for child process to die waitpid($pid, 0); return $self; } 1; __END__ =head1 NAME IPC::Open3::Simple - A simple alternative to IPC::Open3 =head1 VERSION $Id: Simple.pm,v 1.7 2006/07/20 13:30:02 erwan Exp $ =head1 SYNOPSIS To run 'ls' in a few directories and put the returned lines in a list: my @files; my $ipc = IPC::Open3::Simple->new(out => sub { push @files, $_[0]; }) $ipc->run('ls /etc/'); $ipc->run('ls /home/erwan/'); To run a 'cvs up' and do different stuff with what cvs writes to stdout and stderr: IPC::Open3::Simple->new(out => \&parse_cvs_stdout, err => \&parse_cvs_stderr)->run('cvs up'); =head1 DESCRIPTION IPC::Open3::Simple aims at making it very easy to start a shell command, eventually feed its stdin with some data, then retrieve its stdout and stderr separately. When you want to run a shell command and parse its stdout/stderr or feed its stdin, you often end up using IPC::Run, IPC::Cmd or IPC::Open3 with your own parsing code, and end up writing more code than you intended. IPC::Open3::Simple is about removing this overhead and making IPC::Open3 easier to use. IPC::Open3::Simple calls IPC::Open3 and redirects stdin, stdout and stderr to some function references passed in argument to the constructor. It does a select on the input/output filehandles returned by IPC::Open3 and dispatches their content to and from those functions. =head1 INTERFACE =over =item my $runner = IPC::Open3::Simple->B(in => \&sub_in, out => \&sub_out, err => \&sub_err) Return an object that run commands. Takes no arguments or a hash containing one or more of the keys 'in', 'out' and 'err'. The values of those keys are function references (see method I for details). =item $runner->B(@cmds) Execute the shell commands I<@cmds>. I<@cmds> follows the same syntax as the command arguments of I from IPC::Open3. I creates a process that executes thoses commands, and connects the process's stdin, stdout and stderr to the functions passed in the constructor: If I was defined in I, every line coming from the process's stdout is passed as first argument to the function reference I. The line is chomped. If I was defined, the same applies, with lines from the process's stderr being passed to I. If I was defined, I is called with a filehandle as first argument. Everything written to this filehandle will be sent forward to the process's stdin. I is responsible for calling I() on the filehandle. I returns only when the command has finished to run. =back =head1 DIAGNOSTICS =over =item "IPC::Open3::Simple::new expects coderefs" You called I with 'in', 'err' or 'out' arguments that are not function references. =item "ERROR: failed to execute command..." Open3 failed to run the command passed in I<@cmds> to I. =back =head1 BUGS AND LIMITATIONS No bugs so far. Limitation: IPC::Open3::Simple is not designed for interactive interprocess communication. Do not use it to steer the process opened by I via stdin/stdout/stderr, use fork and pipes or some appropriate IPC module for that. IPC::Open3::Simple's scope is to easily run a command, eventually with some stdin input, and get its stdout and stderr along the way, not to interactively communicate with the command. =head1 SEE ALSO See IPC::Open3, IPC::Run, IPC::Cmd. =head1 COPYRIGHT AND LICENSE Copyright (C) by Erwan Lemonnier C<< >> This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See L. =cut