++ed by:
SOMMREY
Author image Jörg Sommrey
and 1 contributors

NAME

Shell::Run - Execute shell commands using specific shell

SYNOPSIS

Procedural Interface

        use Shell::Run 'sh';
        my ($input, $output, $rc, $sc);

        # no input
        sh 'echo -n hello', $output;
        print "output is '$output'\n";
        # gives "output is 'hello'"

        # input and output, status check
        $input = 'fed to cmd';
        sh 'cat', $output, $input or warn 'sh failed';
        print "output is '$output'\n";
        # gives "output is 'fed to cmd'"
        
        # insert shell variable
        sh 'echo -n $foo', $output, undef, foo => 'var from env';
        print "output is '$output'\n";
        # gives "output is 'var from env'"

        # special bash feature
        use Shell::Run 'bash';
        bash 'cat <(echo -n $foo)', $output, undef, foo => 'var from file';
        print "output is '$output'\n";
        # gives "output is 'var from file'"

        # change export name
        use Shell::Run 'sea-shell.v3' => {as => 'seash3'};
        seash3 'echo hello', $output;

        # specify program not in PATH
        use Shell::Run sgsh => {exe => '/opt/shotgun/shell'};
        sgsh 'fire', $output;

        # not a shell
        use Shell::Run sed => {args => ['-e']};
        sed 's/fed to/eaten by/', $output, $input;
        print "output is '$output'\n";
        # gives "output is 'eaten by cmd'"

        # look behind the scenes
        use Shell::Run sh => {debug => 1, as => 'sh_d'};
        sh_d 'echo -n', $output;
        # gives:
        ## using shell: /bin/sh -c
        ## executing cmd:
        ## echo -n
        ##
        ## closing output from cmd
        ## cmd exited with rc=0

        # remove export
        no Shell::Run qw(seash3 sgsh);
        # from here on seash3 and sgsh are no longer known
        # use aliased name (seash3) if provided!

        # capture command status code
        ($sc, $rc) = sh 'exit 2';
        # status code $sc is 2, return code $rc is false

OO Interface

        use Shell::Run;

        my $bash = Shell::Run->new(name => 'bash');

        my ($input, $output);

        # input and output, status check
        $input = 'fed to cmd';
        $bash->run('cat', $output, $input) or warn('bash failed');
        print "output is '$output'\n";
        
        # everything else analogous to the procedural interface
        

DESCIPTION

The Shell::Run module provides an alternative interface for executing shell commands in addition to

  • qx{cmd}

  • system('cmd')

  • open CMD, '|-', 'cmd'

  • open CMD, '-|', 'cmd'

  • IPC::Run

While these are convenient for simple commands, at the same time they lack support for some advanced shell features.

Here is an example for something rather simple within bash that cannot be done straightforward with perl:

        export passwd=secret
        key="$(openssl pkcs12 -nocerts -nodes -in somecert.pfx \
                -passin env:passwd)"
        signdata='some data to be signed'
        signature="$(echo -n "$signdata" | \
                openssl dgst -sha256 -sign <(echo "$key") -hex"
        echo "$signature"

As there are much more openssl commands available on shell level than via perl modules, this is not so simple to adopt. One had to write the private key into a temporary file and feed this to openssl within perl. Same with input and output from/to the script: one has to be on file while the other may be written/read to/from a pipe.

Other things to consider:

  • There is no way to specify by which interpreter qx{cmd} is executed.

  • The default shell might not understand constructs like <(cmd).

  • perl variables are not accessible from the shell.

Another challenge consists in feeding the called command with input from the perl script and capturing the output at the same time. While this last item is perfectly solved by IPC::Run, the latter is rather complex and even requires some special setup to execute code by a specific shell.

The module Shell::Run tries to merge the possibilities of the above named alternatives into one. I.e.:

  • use a specific command interpreter e.g. bash.

  • provide the command to execute as a single string, like in system()

  • give access to the full syntax of the command interpreter

  • enable feeding of standard input and capturing standard output of the called command

  • enable access to perl variables within the called command

  • easy but flexible usage

Using the Shell::Run module, the above given shell script example might be implemented this way in perl:

        use Shell::Run 'bash';

        my $passwd = 'secret';
        my $key;
        bash 'openssl pkcs12 -nocerts -nodes -in demo.pfx \
                -passin env:passwd', $key, undef, passwd => $passwd;
        my $signdata = 'some data to be signed';
        my $signature;
        bash 'openssl dgst -sha256 -sign <(echo "$key") -hex',
                 $signature, $signdata, key => $key;
        print $signature;

Quite similar, isn't it?

Actually, the call to openssl dgst as above was the very reason to create this module.

Commands run by Shell::Run are by default executed via the -c option of the specified shell. This behaviour can be modified by providing other arguments in the use statement or the constructor Shell::Run->new.

Debugging output can be enabled in a similar way.

Procedural vs OO interface

The procedural interface acts as a wrapper for the OO interface with a hidden object instance. Despite syntax, there is no difference in funtionallity between

$sh->run('something', ...)

and

sh 'something', ...

The only difference is when the instance of Shell::Run is created. With use Shell::Run 'shell' it happens in a BEGIN block and with $shell = Shell::Run->new(name => 'shell') at runtime.

So use the OO interface

  • if you like it more

  • if need a reference to the run subroutine

  • if you want to catch errors from the new constructor at runtime

and use the procedural interface

  • if you like it more

  • if you prefer a terse syntax

USAGE

The procedural interface's behaviour can be configured by arguments given to the use statement. Providing arguments to the use Shell::Run statement is mandatory for the procedural interfaces as nothing will be exported by default.

use Shell::Run qw(name...)

Searches every given name in PATH and exports a subroutine of the same name for each given argument into the caller for accessing the specified external programs.

use Shell::Run name => options, ...

Export a subroutine into the caller for accessing an external program. Unless otherwise specified in options, search for an executable named name in PATH and export a subroutine named name

options must be a hash reference as follows:

exe => executable

Use executable as the path to an external program. Disables a PATH search.

args => arguments

Call the specified external program with these arguments. Must be a reference to an array.

Default: ['-c'].

as => export

Use export as the name of the exported subroutine.

debug => debug

Provide debugging output to STDERR if debug has a true value.

encoding => encoding

Specify encoding for input and output data. Defaults to UTF-8.

FUNCTIONS

name cmd, output, [input, [key => value,...]]

Call external program configured as name.

cmd

The code that is to be executed by this shell.

output

A scalar that will receive STDOUT from cmd. The content of this variable will be overwritten.

input

An optional scalar holding data that is fed to STDIN of cmd

key => value, ...

A list of key-value pairs that are set in the environment of the called shell.

In scalar context, returns true or false according to the exit status of the called command. In list context, returns two values: the completion code of the executed command and the exit status as the logical negation of the completion code from a perl view.

METHODS

Constructor

Shell::Run->new([options])

options (if provided) must be a hash as follows:

name => name

Searches name in PATH for an external program to be used.

This value is ignored if executable is given and defaults to sh.

exe => executable

Use executable as the path to an external program. Disables a PATH search.

args => arguments

Call the specified external program with these arguments. Must be a reference to an array.

Default: ['-c'].

debug => debug

Provide debugging output to STDERR if debug has a true value.

Methods

$sh->run(cmd, output, [input, [key => value, ...]])

cmd

The code that is to be executed by this shell.

output

A scalar that will receive STDOUT from cmd. The content of this variable will be overwritten.

input

An optional scalar holding data that is fed to STDIN of cmd

key => value, ...

A list of key-value pairs that are set in the environment of the called shell.

In scalar context, returns true or false according to the exit status of the called command. In list context, returns two values: the completion code of the executed command and the exit status as the logical negation of the completion code from a perl view.

BUGS AND LIMITATIONS

There seems to be some race condition when the called script closes its input file prior to passing all provided input data to it. Sometimes a SIGPIPE is caught and sometimes syswrite returns an error. It is not clear if all situations are handled correctly.

Best effort has been made to avoid blocking situations where neither reading output from the script nor writing input to it is possible. However, under some circumstance such blocking might occur.

SEE ALSO

For more advanced interaction with background processes see IPC::Run.

AUTHOR

Jörg Sommrey

LICENCE AND COPYRIGHT

Copyright (c) 2019, Jörg Sommrey. All rights reserved.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.