NAME

IPC::Open3::Utils - Functions for facilitating some of the most common open3() uses

VERSION

This document describes IPC::Open3::Utils version 0.1

DESCRIPTION

The goals of this module are:

1 Encapsulate logic done every time you want to use open3().
2 Out of the box printing to STDOUT/STDERR or assignments to variables (see #5)
3 Provide access to $? and $! like you have with system()
4 open3() error reporting
5 comprehensive but simple output processing handlers for flexibility (see #2)
6 Lightweigt utilities for examamining the menaing of $? without POSIX

SYNOPSIS

use IPC::Open3::Utils qw(run_cmd put_cmd_in ...);


run_cmd(@cmd); # like 'system(@cmd)' but w/ out $?

# like 'if (system(@cmd) != 0)' but w/ out $?
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, 'child_error' => 1, })) {
    print "open3() said: $open3_error\n" if $open3_error;
    
    if ($!) {
        print int($!) . ": $!\n";
    }
    
    if ($? ne '') {
        # we already know its not but we could use: 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($?);
        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!

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.

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;
},
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.

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. The default is to use readline()

If 'child_error' is set and 'read_length_bytes' is less than 128 then 'read_length_bytes' gets reset to 128.

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() 
}
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

Setting this to a true allows for "Getting the Child Error Code (IE $?) from an open3() call".

This means, in short that $? and $! will be set like it is after a system() call.

If the value is a scalar reference then the value of the SCALAR refernced will be the Child Error Code of the command (IE $?)

Other related values are:

child_error_errno

A SCALAR reference whose value will be the error number (IE: $! or ERRNO) of the command.

child_error_uniq

A unique identifier string to be used internally. Defaults to rand()

child_error_wrapper

The path to the "ipc_open3_utils_wrap" script. Defaults to './ipc_open3_utils_wrap' if it is executable or else 'ipc_open3_utils_wrap' and assumes its in the PATH.

child_error_uniq_mismatch

A code ref that will be called if the output looks like a "child error" line from ipc_open3_utils_wrap but whose uniq identifier does not match 'child_error_uniq'.

It gets passed the uniq id it found, the uniq id it expected, the child error it had ($?), the errno it had ($!), the current line, the wrapper script's $0.

If your code ref returns true it will stop the while loop over the command. Otherwise/by default it will simply not invoke the handler for that line and go to the next line.

child_error_wrapper_used

A SCALAR reference used to store the name of the script that ended up being used as the child_error_wrapper.

Getting the Child Error Code (IE $?) from an open3() call

This functionality is acheived by wrapping the command by a script that calls system() then outputs a specially formatted line indicating the values we are interested in.

If anyone has a better way to do this I'd be very ineterested!

The script can be in ., PATH, or specified via the "%args" key 'child_error_wrapper'.

It can be created with this convienience function:

create_ipc_open3_utils_wrap_script();

The first, optional argument, is the file to create/update. Defaults to /usr/bin/ipc_open3_utils_wrap.

The second, optional argument, is the mode. Defaults to 0755.

It is not recommended to call it something else besides 'ipc_open3_utils_wrap' as it will require the 'child_error_wrapper' key all the time.

Returns true on success, false other wise. Be sure to check $! for the reason why it failed.

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.

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