FredMan
FredMan

Reputation: 829

Redirect STDERR in OPEN pipe comand. Perl Linux

The version of perl I'm restricted to is v5.10.0;

I have a script, "script1" that needs to call another script "script2."

In order to bypass the shell and avoid command line injection I am using "open" to do this in perl.

open(my $fh, "-|", "/path/to/script2", "-n", "$param") or say "failure $@";

I can not modify script2.

I need to get the output of the stderr from script2 into a variable in script1.

I got the "-|" syntax from https://perldoc.perl.org/functions/open.html but the description there is mostly an example of how to do it and not what it is doing so I can't figure out how to redirect err to out or even at least redirect err to some other variable.

I'm hoping for something that will look similar to one of these two options:

# option 1)
open(my $fh, "-|", "/path/to/script2", "-n", "$param") or say "failure $@";
while($line = <$fh>) {  # stderr and stdout are both fed to $line
     print $line;
}

# option 2)
open(STDERR, "+<", $stderr);
open(my $fh, "-|", "/path/to/script2", "-n", "$param") or say "failure $@";
while($line = <$fh>) {  #stdout is fed to $line
     print $line;
}
while($line = <$stderr>) {  #stderr from script2 is fed to $line
     print $line;
}

Upvotes: 2

Views: 972

Answers (2)

zdim
zdim

Reputation: 66873

If the objective is to capture streams one nice module is Capture::Tiny

use warnings;
use strict;
use Capture::Tiny qw(capture);

my $cmd  = '/path/to/script2';
my @args = ('-n', $param);

my ($stdout, $stderr, $exit) = capture {
    system($cmd, @args);
};

You can put the command and arguments in a list, @cmd = ('ls', '-l', './'). If the command and arguments are lumped into a scalar the shell may get used (if there are shell metacharacters).

This portable module allows you to

capture almost anything sent to STDOUT or STDERR, regardless of whether it comes from Perl, from XS code or from an external program.

There is also capture_stderr function if you only want that stream.

Note that many tools, including system, have a provision for bypassing the shell: pass command and/or arguments as a list. (However, if you need STDERR you cannot get it directly with system alone, without using shell redirection.)


If there is a possibility that @args would be empty this should be invoked as

system ( {$cmd} $cmd, @args );

and the shell is still avoided. A nice way to try it out is with $cmd = 'echo "From shell"'. Thanks to ikegami for noting all this in a comment.

See exec for this use of the indirect object notation, which

forces interpretation of the LIST as a multivalued list, even if there is only a single scalar in the list

and this ensures that the shell is never called.

Upvotes: 3

H&#229;kon H&#230;gland
H&#229;kon H&#230;gland

Reputation: 40718

I would do this using IPC::Run3:

use strict;
use warnings;
use IPC::Run3;

run3 ['script2.pl', '-n', $param], undef, \my $out, \my $err;

This will give you the STDERR of script2.pl in the variable $err.

From the documentation:

run3($cmd, $stdin, $stdout, $stderr, \%options)

All parameters after $cmd are optional.

The parameters $stdin, $stdout and $stderr indicate how the child's corresponding filehandle (STDIN, STDOUT and STDERR, resp.) will be redirected. Because the redirects come last, this allows STDOUT and STDERR to default to the parent's by just not specifying them -- a common use case.

$cmd

Usually $cmd will be an ARRAY reference and the child is invoked via

system @$cmd;

Note that passing $cmd as an array reference will avoid running the command through the shell under the same conditions as for the system() command

Upvotes: 4

Related Questions