geek geek - 3 months ago 13x
Perl Question

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 ./ 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");
print"Error = $stderr";
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.


When an error occurs it is most likely not going to be in STDOUT, and even 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 thus 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 will need further investigation. Docs don't say what exactly it is, but I'd expect the unpacked code discussed above (the question indicates that this is indeed so). Here $ssh is only the vehicle to execute a command, it cannot know what went wrong with that command. What it gets back is the command's exit code, apparently already unpacked. To figure out what exactly that means you need to look up the command's docs. Or, you can modify the command to get the STDERR on the STDOUT, see the section added 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';

Now you will get the error that you see at the terminal ("no such file or directory") printed on STDOUT, thus 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 if you need to see what is printed on STDERR (but code above 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.