IPC::Open3::Utils - simple API encapsulating the most common open3() logic/uses including handling various corner cases and caveats
This document describes IPC::Open3::Utils version 0.92
The goals of this module are:
use IPC::Open3::Utils qw(run_cmd put_cmd_in ...); run_cmd(@cmd); # like 'system(@cmd)' # like 'if (system(@cmd) != 0)' if (!run_cmd(@cmd)) { print "Oops you may need to re-run that command, it failed.\n1"; }
So far not too useful but its when you need more complex things than system()-like behavior (and why you are using open3() to begin with one could assume) that this module comes into play.
If you care about exactly what went wrong you can get very detailed:
my $open3_error; if (!run_cmd(@cmd, {'open3_error' => \$open3_error})) { print "open3() said: $open3_error\n" if $open3_error; if ($!) { print int($!) . ": $!\n"; } if ($?) { # or if (!child_error_ok($?)) { print "Command failed to execute.\n" if child_error_failed_to_execute($?); print "Command seg faulted.\n" if child_error_seg_faulted($?); print "Command core dumped.\n" if child_error_core_dumped($?); unless ( child_error_failed_to_execute($?) ) { print "Command exited with signal: " . child_error_exit_signal($?) . ".\n"; print "Command exited with value: " . child_error_exit_value($?) . ".\n"; } } }
You can slurp the output into variables:
# both STDOUT/STDERR in one my @output; if (put_cmd_in(@cmd, \@output)) { print _my_stringify(\@output); } # seperate STDOUT/STDERR my @stdout; my $stderr; if (put_cmd_in(@cmd, \@stdout, \$stderr)) { print "The command ran ok\n"; print "The output was: " . _my_stringify(\@stdout); if ($stderr) { print "However there were errors reported:" . _my_stringify($stderr); } }
You can look for a certain piece of data then stop processing once you have it:
my $widget_value; run_cmd(@cmd, { 'handler' => sub { my ($cur_line, $stdin, $is_stderr, $is_open3_err, $short_circuit_loop_boolean_scalar_ref) = @_; if ($cur_line =~ m{^\s*widget_value:\s*(\d+)}) { $widget_value = $1; ${ short_circuit_loop_boolean_scalar_ref } = 1; } return 1; }, }); if (defined $widget_value) { print "You Widget is set to $widget_value."; } else { print "You do not have a widget value set."; }
You can do any or all of it!
All functions can be exported.
run_cmd() and put_cmd_in() are exported by default and via ':cmd'
:all will export, well, all functions
:err will export all child_error* functions.
Both of these functions:
take an array containing the command to run through open3() as its first arguments
take an optional configuration hashref as the last argument (described below in "%args")
return true if the command was executed successfully and false otherwise.
run_cmd(@cmd) run_cmd(@cmd, \%args)
By default the 'handler' (see "%args" below) prints the command's STDOUT and STDERR to perl's STDOUT and STDERR.
Same %args as run_cmd() but it overrides 'handler' with one that populates the given "output" refs.
You can have one "output" ref to combine the command's STDERR/STDOUT into one variable. Or two, one for STDOUT and one for STDERR.
The ref can be an ARRAY reference or a SCALAR reference and are specified after the command and before the args hashref (if any)
put_cmd_in(@cmd, \@all_output, \%args) put_cmd_in(@cmd, \$all_output, \%args) put_cmd_in(@cmd, \@stdout, \@stderr, \%args) put_cmd_in(@cmd, \$stdout, \$stderr, \%args)
To not waste memory on one that you are not interested in simply pass it undef for the one you don't care about.
put_cmd_in(@cmd, undef, \@stderr, \%args); put_cmd_in(@cmd, \@stdout, undef, \%args)
Or quiet it up completely.
put_cmd_in(@cmd, undef, undef, \%args)
or progressivley getting simpler:
put_cmd_in(@cmd, undef, \%args); put_cmd_in(@cmd, \%args); put_cmd_in(@cmd);
Note that using one "output" ref does not gaurantee the output will be in the same order as it is when you execute the command via the shell due to the handling of the filehandles via IO::Select. Due to that occasionally a test regarding single "output" ref testing will fail. Just run it again and it should be fine :)
This is an optional 'last arg' hashref that configures behavior and functionality of run_cmd() and put_cmd_in()
Below are the keys and a description of their values.
A code reference that should return a boolean status. If it returns false run_cmd() and put_cmd_in() will also return false.
If it returns true and assuming open3() threw no errors that'd make them return false then run_cmd() and put_cmd_in() will return true.
Any exceptions thrown in the handler are caught and put in $@. Then the open3 cleanup happens and the function returns false.
It gets the following arguments sent to it:
This is useful for efficiency so you can stop processing the command once you get what you're interested in and still return true overall.
'handler' => sub { my ($cur_line, $stdin, $is_stderr, $is_open3_err, $short_circuit_loop_boolean_scalar_ref) = @_; ... return 1; },
The number of seconds you want to allow the execution to run. If it takes longer than the specified amount, it sets $@ to "Alarm clock" ($! will probably be 4), does the open3 cleanup, and returns false.
If Time::HiRes's alarm() is available it uses that instead of alarm(). In that case you can set its value to the number of microseconds you want to allow it to run.
run_cmd( @cmd, { 'timeout' => 42 } ); run_cmd( @cmd, { 'timeout' => 3.14159 } ); # The '.14159' is sort of pointless unless you've brought in Time::HiRes
Any previous alarm is set back to what it was once it is complete.
All normal alarm/sleep/computer time caveats apply. That includes mixing large normal alarm() w/ HiRes alarms. For example, in the first command below it seems like Time::HiRes's alarm() should be much closer to 10K but it says over 8.5K seconds have elapsed, the second looks like what we expect:
$ perl -mTime::HiRes -le 'print alarm(10_000);print Time::HiRes::alarm(100.1);print alarm(0);' 0 1410.065364 101 $ $ perl -mTime::HiRes -le 'print alarm(1_000);print Time::HiRes::alarm(100.1);print alarm(0);' 0 999.999876 101 $
If you want to specify a microsecond timeout you can set 'timeout_is_microseconds' to true.
If Time::HiRes's ualarm() is not available the value is turned into seconds and a normal alarm is used. If this happens and the result is under one second then the alarm is set for 1 second.
use Time::HiRes; run_cmd( 'blink', { 'timeout' => 350_001, 'timeout_is_microseconds' => 1 } );
Boolean to have the command's STDIN closed immediately after the open3() call.
If this is set to true then the stdin variable in your handler's arguments will be undefined.
The value can be one of three types:
String to pass to the command's stdin via IO::Handle's printflush() method.
Array ref of strings to pass to the command's stdin via IO::Handle's printflush() method.
Code ref that returns one or more strings to pass to the command's stdin via IO::Handle's printflush() method.
The value of this can be 'stderr' or 'stdout' and will cause the named handle to not even be included in the while() loop and hence never get to the 'handler'.
This might be useful to, say, make run_cmd() only print the command's STDERR.
run_cmd(@cmd); # default handler prints the command's STDERR and STDOUT to perl's STDERR and STDOUT run_cmd(@cmd, { 'ignore_handle' => 'stdout' }); # only print the command's STDERR to perl's STDERR run_cmd(@cmd, { 'ignore_handle' => 'stderr' }); # only print the command's STDOUT to perl's STDOUT
This is a hashref that tells which, if any, handles you want autoflush turned on for (IE $handle->autoflush(1) See IO::Handle).
It can have 3 keys whose value is a boolean that, when true, will turn on the handle's autoflush before the open3() call.
Those keys are 'stdout', 'stderr', 'sdtin'
run_cmd(@cmd, { 'autoflush' => { 'stdout' => 1, 'stderr' => 1, 'stdin' => 1, # open3() will probably already have done this but just in case you want to be explicit }, });
Number of bytes to read from the command via sysread (minimum 128). The default is to use readline()
This is the key that any open3() errors get put in for post examination. If it is a SCALAR ref then the error will be in the variable it references.
my %args; if (!run_cmd(@cmd,\%args)) { # $args{'open3_error'} will have the error if it was from open3() }
As of verison 0.8 this will typically also be in $@. (See note in TODO)
Boolean to carp() errors from open3() itself. Default is false.
Boolean to quit the loop if an open3() error is thrown. This will more than likley happen anyway, this is just explicit. Default is false.
Each of these child_error* functions opertates on the value of $? or the argument you pass it.
Returns true if the value indicates success.
if ( child_error_ok(system(@cmd)) ) { print "The command was run successfully\n"; }
Returns true if the value indicates failure to execute.
Returns true if the value indicated that the execution had a segmentaton fault
Returns true if the value indicated that the execution had a core dump
Returns the exit signal that the value represents
Returns the exit value that the value represents
Throws no warnings or errors of its own. Capturing errors associated with a given command are documented above.
IPC::Open3::Utils requires no configuration files or environment variables.
IPC::Open3, IO::Handle, IO::Select
None reported.
No bugs have been reported.
Please report any bugs or feature requests to bug-ipc-open3-utils@rt.cpan.org, or through the web interface at http://rt.cpan.org.
bug-ipc-open3-utils@rt.cpan.org
- autoflush() by default ? - if not closed && !autoflushed() finish read ? - Add 'blocking' $io->blocking($value) ? - Add filehandle support to put_cmd_in() - find out why $! seems to always be 'Bad File Descriptor' on some systems - no_hires_timeout attribute to forceusing built in alarm() even when Time::HiRes functions are available ? - drop post-open3() call open3_error logic since it is caught immediately and put in $@ or is it possible it can peter out ambiguously later ? - open3 eval under alarm
Daniel Muey <http://drmuey.com/cpan_contact.pl>
<http://drmuey.com/cpan_contact.pl>
Copyright (c) 2008, Daniel Muey <http://drmuey.com/cpan_contact.pl>. All rights reserved.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic.
BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.1
To install IPC::Open3::Utils, copy and paste the appropriate command in to your terminal.
cpanm
cpanm IPC::Open3::Utils
CPAN shell
perl -MCPAN -e shell install IPC::Open3::Utils
For more information on module installation, please visit the detailed CPAN module installation guide.