Search code examples
perlstderropenssh

unable to capture stderr while performing openssh to a variable- perl


I want to capture the standard error displayed on host machine after (ssh->capture) to a variable.

for example when i try:

  use Net::OpenSSH;

my $ssh = Net::OpenSSH->new($host);
my $out=$ssh->capture("cd /home/geek");
 $ssh->error and
die "remote cd command failed: " . $ssh->error;

out put is:

 child exited with code 1 at ./change_dir.pl line 32

i am not able to see what is the error. i get no such file or directory on the terminal. I want to capture the same "no such file or director" in $out.

example 2,

my ($stdout,$stderr)=$ssh->capture("cd /home/geek");
if($stderr)
print"Error = $stderr";
else
print "$stdout"

i see "Error=" printed but does not seee that $stderr on the screen. i see $stdout is printed on success but print $stderr does not get printed only"Error= " gets printed.


Solution

  • When an error occurs it is most likely not going to be in STDOUT, and if it is in STDERR you are not catching that. You need to get to the application's exit code, in the following way. (Given the update to the question which I only see now: See the end for how to get STDERR.)

    After the capture method you want to examine $? for errors (see Net-OpenSSH). Unpack that to get to the exit code returned by what was actually run by $ssh, and then look in that application's docs to see what that code means

    $exit_code = $?;
    if ($exit_code) {
        $app_exit = $exit_code >> 8; 
        warn "Error, bit-shift \$? --> $app_exit";
    }
    

    The code to investigate is $app_exit.

    An example. I use zip in a project and occasionally catch the error of 3072 (that is the $?). When that's unpacked as above I get 12, which is zip's actual exit. I look up its docs and it nicely lists its exit codes and 12 means Nothing to update. That's the design decision for zip, to exit with 12 if it had no files to update in the archive. Then that exit gets packaged into a two-byte number (in the upper byte), and that is returned and so it is what I get in $?.

    Failure modes in general, from system in Perl docs

    if    ($? == -1) { warn "Failed to execute -- " }
    elsif ($? & 127) { 
        $msg = sprintf("\tChild died with signal %d, %s coredump -- ",
            ($? & 127),  ($? & 128) ? 'with' : 'without');
        warn $msg;
    } else { 
        $msg = sprintf("\tChild exited with value %d -- ", $? >> 8);
        warn $msg;
    }
    

    The actual exit code $? >> 8 is supplied by whatever ran and so its interpretation is up to that application. You need to look through its docs and hopefully its exit codes are documented.


    Note that $ssh->error seems designed for this task. From the module's docs

    my $output = $ssh->capture({ timeout => 10 }, "echo hello; sleep 20; echo bye");
    $ssh->error and warn "operation didn't complete successfully: ". $ssh->error;
    

    The printed error needs further investigation. Docs don't say what it is, but I'd expect the unpacked code discussed above (the question update indicates this). Here $ssh only runs a command and it doesn't know what went wrong. It merely gets back the command's exit code, to be looked at.

    Or, you can modify the command to get the STDERR on the STDOUT, see below.


    The capture method is an equivalent of Perl's backticks (qx). There is a lot on SO on how to get STDERR from backticks, and Perl's very own FAQ has that nicely written up in perlfaq8. A complication here is that this isn't qx but a module's method and, more importantly, it runs on another machine. However, the "output redirection" method should still work without modifications. The command (run by $ssh) can be written so that its STDERR is redirected to its STDOUT.

    $cmd_all_output = 'your_whole_command 2>&1';
    $ssh->capture($cmd_all_output);
    

    Now you will get the error that you see at the terminal ("no such file or directory") printed on STDOUT and so it will wind up in your $stdout. Note that one must use sh shell syntax, as above. There is a big bit more to it so please look it up (but this should work as it stands). Most of the time it is the same message as in the exit code description.

    The check that you have in your code is good, the first line of defense: One should always check $? when running external commands, and for this the command to run need not be touched.