Skip to content
Advertisement

Redirect STDERR to a file from Perl in Linux

So, I was trying to catch the error messages from running some basic Linux command, using Perl. For example, I try to catch the STDERR when running the ldd command:

# The stderr_file already exists
my $cmd = "ldd $file 2>$stderr_file";
my $output = `$cmd`;

But, even if the output of the ldd command does contain error messages such as ldd: warning: you do not have execution permission for, it won’t print them in to the $stderr_file, and I wonder why.

Then I tried to run the command myself: ldd /some/path/to/file 2>./error.log and it failed with: ldd: ./2: No such file or directory.

I suspect that the reason is due to the fact my Linux uses Tcsh because if I switch to Bash, the command works.

How should I approach this issue and solve it?

Also, I read some previous thread but didn’t find any related thread or method to solve it.

Advertisement

Answer

When interpolating strings into shell commands that are intended to be single arguments, you should always use String::ShellQuote to avoid bugs where the shell parses unintended metacharacters in your strings (including space characters). It only implements bourne shell quoting though, so it may not be compatible with tcsh either – but Perl is usually configured to use /bin/sh, which should be bourne shell compatible.

use strict;
use warnings;
use String::ShellQuote;
my $cmd = 'ldd ' . shell_quote($file) . ' 2>' . shell_quote($stderr_file);

As an alternative, you can avoid the shell entirely by using the list form of system() and redirecting STDERR within Perl. Capture::Tiny makes this easy.

use strict;
use warnings;
use Capture::Tiny 'capture';
use Path::Tiny;
my ($out, $err, $exit_code) = capture { system 'ldd', $file };
# error checking for system() call here
path($stderr_file)->spew_raw($err);

(Path::Tiny is just an example, you can also use File::Slurper or open the file and write to it yourself with appropriate error checking.)

The core module IPC::Open3 can also be used to capture STDERR separately and avoid the shell, somewhat more manually.

use strict;
use warnings;
use IPC::Open3;
use Symbol 'gensym';
my $pid = open3 undef, my $stdout, my $stderr = gensym, 'ldd', $file;
my ($out, $err);
{
  local $/;
  $out = readline $stdout;
  $err = readline $stderr;
}
waitpid $pid, 0;
my $exit_code = $? >> 8;

This can run into deadlocks if the process outputs a sufficient amount to STDERR. I highly recommend making use of Capture::Tiny instead as above, or IPC::Run or IPC::Run3 for more flexibility.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement