Search code examples
perl

Suppress / catch / trap error message on failed fork open() in Perl


Consider:

#!/usr/bin/perl

use strict;
use warnings;

my $command = "nosuchcommand"

my $rc = open( my $fh, "-|", "$command" );

if ( ! defined( $rc ) )
{
    print "My own error message\n";
}
else
{
    system( "ps -p $rc" );
}

Expected output:

My own error message

Observed output:

Can't exec "nosuchcommand": No such file or directory at ./mcre line 8.
My own error message

How can I catch / trap / suppress the error message generated by Perl on a failing open-pipe (in favor of my own error handling)?

Further observations:

  • If I open( my $fh, "-|", "$command 2>&1" );, I get the expected output. But in case of success of $command, I would get the STDERR of $command mixed into STDOUT, and I don't want that.

  • If I open( my $fh, "-|", "$command 2>/dev/null" );, I get this:

    PID TTY          TIME CMD
  15143 pts/0    00:00:00 sh <defunct>

So the specific redirecion of 2>/dev/null (instead of 2>&1) changed the return value of open... this was somewhat surprising, but is not the core of my question.

On IPC::Run

This has been suggested in two answers by now. I understand that it might be the "correct" answer in the general case, but would really prefer not to (as IPC::Run is not an option for me on one very significant target platform that comes without it and does not offer an easy way to install it).


Solution

  • Can't exec "nosuchcommand": No such file or directory at ./mcre line 8.
    

    This line is actually a "warning", enabled by use warnings;. From perldiag:

    Can't exec "%s": %s

    (W exec) A system(), exec(), or piped open call could not execute the named program for the indicated reason. Typical reasons include: the permissions were wrong on the file, the file wasn't found in $ENV{PATH}, the executable in question was compiled for another architecture, or the #! line in a script points to an interpreter that can't be run for similar reasons. (Or maybe your system doesn't support #! at all.)

    Now, use warnings; is highly recommended, so just removing it entirely is not a "solution". But it is possible to disable warnings within a given scope via no warnings <category>. So, to suppress that particular message:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my $command = "nosuchcommand";
    my @args = ();
    my $rc;
    my $fh;
    
    {
        no warnings qw( exec );
        my $rc = open( my $fh, "-|", "$command", @args );
    }
    
    if ( ! defined( $rc ) )
    {
        print "My own error message: $!\n";
    }
    

    Output:

    My own error message: No such file or directory
    

    The part of the error message generated by the underlying shell ("No such file or directory") is in the $! special variable. Note that you should be using the four-parameter version of open (with an empty LIST if need be, as pictured above). Perl has different methods of executing external commands, one going through /bin/sh -c (and not being under control of the exec warning), and the other through execvp (and being under control of said warning). The four-parameter version of open ensures that Perl will use the latter.