6

I want to execute an external command from within my Perl script, putting the output of both stdout and stderr into a $variable of my choice, and to get the command's exit code into the $? variable.

I went through solutions in perlfaq8 and their forums, but they're not working for me. The strange thing is that I don't get the output of sdterr in any case, as long as the exit code is correct.

I'm using Perl version 5.8.8, on Red Hat Linux 5.

Here's an example of what I'm trying:

my $cmd="less";
my $out=`$cmd 2>&1`;

or

my $out=qx($cmd 2>&1);

or

open(PIPE, "$cmd 2>&1|");

When the command runs successfully, I can capture stdout.

I don't want to use additional capture modules. How can I capture the full results of the external command?

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Gregory Danenberg
  • 519
  • 2
  • 9
  • 15

4 Answers4

11

This was exactly the challenge that David Golden faced when he wrote Capture::Tiny. I think it will help you do exactly what you need.

Basic example:

#!/usr/bin/env perl

use strict;
use warnings;

use Capture::Tiny 'capture';

my ($stdout, $stderr, $return) = capture {
  system( 'echo Hello' );
};

print "STDOUT: $stdout\n";
print "STDERR: $stderr\n";
print "Return: $return\n";

After rereading you might actually want capture_merged to join STDOUT and STDERR into one variable, but the example I gave is nice and general, so I will leave it.

Joel Berger
  • 20,180
  • 5
  • 49
  • 104
6

Actually, the proper way to write this is:

#!/usr/bin/perl
$cmd = 'lsss';  
my $out=qx($cmd 2>&1);
my $r_c=$?;
print "output was $out\n";
print "return code = ", $r_c, "\n";

You will get a '0' if no error and '-1' if error.

Radix
  • 667
  • 5
  • 28
5

STDERR is intended to be used for errors or messages that might need to be separated from the STDOUT output stream. Hence, I would not expect any STDERR from the output of a command like less.

If you want both (or either) stream and the return code, you could do:

my $out=qx($cmd 2>&1);
my $r_c=$?
print "output was $out\n";
print "return code = ", $r_c == -1 ? $r_c : $r_c>>8, "\n";

If the command isn't executable (perhaps because you meant to use less but wrote lsss instead), the return code will be -1. Otherwise, the correct exit value is the high 8-bits. See system.

JRFerguson
  • 7,426
  • 2
  • 32
  • 36
1

A frequently given answer to this question is to use a command line containing shell type redirection. However, suppose you want to avoid that, and use open() with a command and argument list, so you have to worry less about how a shell might interpret the input (which might be partly made up of user-supplied values). Then without resorting to packages such as IPC::Open3, the following will read both stdout and stderr:

my ($child_pid, $child_rc);

unless ($child_pid = open(OUTPUT, '-|')) {
  open(STDERR, ">&STDOUT");
  exec('program', 'with', 'arguments');
  die "ERROR: Could not execute program: $!";
}
waitpid($child_pid, 0);
$child_rc = $? >> 8;

while (<OUTPUT>) {
  # Do something with it
}
close(OUTPUT);
tijmen
  • 41
  • 2
  • this is the best answer in this thread. with more code, it will prevent using shell redirection, it is usually bash. This example will be better if you detaily comment it. but it is detaily described in perldoc open function. – Znik Feb 21 '17 at 15:00
  • There is important question, why you first wait for child process and check RC, and after that you read its output? I thik it should be swapped. I think it is only one reason it works properly, it is pipe buffering made by the operating system. I think it will hang if external 'program' will produce big stream on stdout or stderr because stream redirection. – Znik Feb 22 '17 at 08:13