How do you capture stderr, stdout, and the exit code all at once, in Perl?

PerlStdoutExit Code

Perl Problem Overview


Is it possible to run an external process from Perl, capture its stderr, stdout AND the process exit code?

I seem to be able to do combinations of these, e.g. use backticks to get stdout, IPC::Open3 to capture outputs, and system() to get exit codes.

How do you capture stderr, stdout, and the exit code all at once?

Perl Solutions


Solution 1 - Perl

(Update: I updated the API for IO::CaptureOutput to make this even easier.)

There are several ways to do this. Here's one option, using the IO::CaptureOutput module:

use IO::CaptureOutput qw/capture_exec/;

my ($stdout, $stderr, $success, $exit_code) = capture_exec( @cmd );

This is the capture_exec() function, but IO::CaptureOutput also has a more general capture() function that can be used to capture either Perl output or output from external programs. So if some Perl module happens to use some external program, you still get the output.

It also means you only need to remember one single approach to capturing STDOUT and STDERR (or merging them) instead of using IPC::Open3 for external programs and other modules for capturing Perl output.

Solution 2 - Perl

If you reread the documentation for IPC::Open3, you'll see a note that you should call waitpid to reap the child process. Once you do this, the status should be available in $?. The exit value is $? >> 8. See $? in perldoc perlvar.

Solution 3 - Perl

If you don't want the contents of STDERR, then the capture() command from IPC::System::Simple module is almost exactly what you're after:

   use IPC::System::Simple qw(capture system $EXITVAL);

   my $output = capture($cmd, @args);

   my $exit_value = $EXITVAL;

You can use capture() with a single argument to invoke the shell, or multiple arguments to reliably avoid the shell. There's also capturex() which never calls the shell, even with a single argument.

Unlike Perl's built-in system and backticks commands, IPC::System::Simple returns the full 32-bit exit value under Windows. It also throws a detailed exception if the command can't be started, dies to a signal, or returns an unexpected exit value. This means for many programs, rather than checking the exit values yourself, you can rely upon IPC::System::Simple to do the hard work for you:

 use IPC::System::Simple qw(system capture $EXIT_ANY);

 system( [0,1], "frobincate", @files);     # Must return exitval 0 or 1

 my @lines = capture($EXIT_ANY, "baznicate", @files);  # Any exitval is OK.

 foreach my $record (@lines) {
     system( [0, 32], "barnicate", $record);  # Must return exitval 0 or 32
 }

IPC::System::Simple is pure Perl, has no dependencies, and works on both Unix and Windows systems. Unfortunately, it doesn't provide a way of capturing STDERR, so it may not be suitable for all your needs.

IPC::Run3 provides a clean and easy interface into re-plumbing all three common filehandles, but unfortunately it doesn't check to see if the command was successful, so you'll need to inspect $? manually, which is not at all fun. Providing a public interface for inspecting $? is something which is on my to-do list for IPC::System::Simple, since inspecting $? in a cross-platform fashion is not a task I'd wish on anyone.

There are other modules in the IPC:: namespace that may also provide you with assistance. YMMV.

All the best,

Paul

Solution 4 - Perl

There are three basic ways of running external commands:

system $cmd;		# using system()
$output = `$cmd`;       # using backticks (``)
open (PIPE, "cmd |");	# using open()

With system(), both STDOUT and STDERR will go the same place as the script's STDOUT and STDERR, unless the system() command redirects them. Backticks and open() read only the STDOUT of your command.

You could also call something like the following with open to redirect both STDOUT and STDERR.

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

The return code is always stored in $? as noted by @Michael Carman.

Solution 5 - Perl

If you're getting really complicated, you might want to try Expect.pm. But that's probably overkill if you don't need to also manage sending input to the process as well.

Solution 6 - Perl

I found IPC:run3 to be very helpful. You can forward all child pipes to a glob or a variable; very easily! And exit code will be stored in $?.

Below is how i grabbed stderr which i knew would be a number. The cmd output informatic transformations to stdout (which i piped to a file in the args using >) and reported how many transformations to STDERR.

use IPC::Run3

my $number;
my $run = run3("cmd arg1 arg2 >output_file",\undef, \undef, \$number);
die "Command failed: $!" unless ($run && $? == 0);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionScoobyView Question on Stackoverflow
Solution 1 - PerlxdgView Answer on Stackoverflow
Solution 2 - PerlMichael CarmanView Answer on Stackoverflow
Solution 3 - PerlpjfView Answer on Stackoverflow
Solution 4 - PerlhoyhoyView Answer on Stackoverflow
Solution 5 - PerlskiphoppyView Answer on Stackoverflow
Solution 6 - PerlIanView Answer on Stackoverflow