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.