NAME
IPC::Open3::Utils - simple API encapsulating the most common open3() logic/uses including handling various corner cases and caveats
VERSION
This document describes IPC::Open3::Utils version 0.92
DESCRIPTION
The goals of this module are:
- 1 Encapsulate logic done every time you want to use open3().
- 2 boolean check of command execution
- 3 Out of the box printing to STDOUT/STDERR or assignments to variables (see #5)
- 4 open3() error reporting
- 5 comprehensive but simple output processing handlers for flexibility (see #3)
- 6 Lightweight utilities for examining the meaning of $? without POSIX
SYNOPSIS
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!
EXPORT
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.
INTERFACE
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()
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.
put_cmd_in()
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 :)
%args
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.
- handler
-
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:
- 1 The current line of the command's output
- 2 The command's STDIN IO::Handle object
- 3 A boolean of whether or not the line is from the command's STDERR
- 4 A boolean of whether or not the line is an error from open3()
- 5 A scalar ref that when set to true will stop the while loop the command is running in.
-
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; },
- timeout
-
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 $
- timeout_is_microseconds
-
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 } );
- close_stdin
-
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.
- pre_read_print_to_stdin
-
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.
- ignore_handle
-
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
- autoflush
-
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 }, });
- read_length_bytes
-
Number of bytes to read from the command via sysread (minimum 128). The default is to use readline()
- open3_error
-
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)
- carp_open3_errors
-
Boolean to carp() errors from open3() itself. Default is false.
- stop_read_on_open3_err
-
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.
Child Error Code Exit code utilities
Each of these child_error* functions opertates on the value of $? or the argument you pass it.
- child_error_ok()
-
Returns true if the value indicates success.
if ( child_error_ok(system(@cmd)) ) { print "The command was run successfully\n"; }
- child_error_failed_to_execute()
-
Returns true if the value indicates failure to execute.
- child_error_seg_faulted()
-
Returns true if the value indicated that the execution had a segmentaton fault
- child_error_core_dumped()
-
Returns true if the value indicated that the execution had a core dump
- child_error_exit_signal()
-
Returns the exit signal that the value represents
- child_error_exit_value()
-
Returns the exit value that the value represents
DIAGNOSTICS
Throws no warnings or errors of its own. Capturing errors associated with a given command are documented above.
CONFIGURATION AND ENVIRONMENT
IPC::Open3::Utils requires no configuration files or environment variables.
DEPENDENCIES
IPC::Open3, IO::Handle, IO::Select
INCOMPATIBILITIES
None reported.
BUGS AND LIMITATIONS
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.
TODO
- 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
AUTHOR
Daniel Muey <http://drmuey.com/cpan_contact.pl>
LICENCE AND COPYRIGHT
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.
DISCLAIMER OF WARRANTY
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