http://www.perlmonks.org?node_id=998671

VinsWorldcom has asked for the wisdom of the Perl Monks concerning the following question:

I have yet another Net::SSH2 question that I haven't found a definitive solution to yet. So here goes.

BACKGROUND

Windows 7 x64 / Strawberry Perl 5.16.1 / Net::SSH2 0.45 - note that Net::SSH2 comes bundled with Strawberry, I did not compile myself.

I've created a simple SSH client using Net::SSH2 and I can connect fine, I've gotten past the freezing issue on Windows ($chan->blocking(0)) and even the issue where the command output lagged one command behind (binmode($chan)), but only sometimes.

I'm testing my client to both CentOS and Cisco router with SSH enabled.

ISSUE

The issue I see is that if I execute a long command (e.g., 'ls -alR /' on linux, 'show run' on Cisco router) I start to see some output and then just get the prompt back. When I issue the next command, I get the remainder of output from the first long command - or maybe just some of it if it's a really long output.

To my novice mind, it seems to be a buffering problem and I've tried the flush() method of Net::SSH2 to no avail. My other thought is that I need to set $| on the $chan filehandle, which also didn't solve it. My final desperation was to actually read the POD line-by-line and found the poll() method and some Google-ing turned up an example of it's usage.

I tried:

my @poll = ({handle => $chan, events => 'in'}); if($ssh2->poll(250, \@poll) && $poll[0]->{revents}->{in}) { print $buf while defined ($len = $chan->read($buf,512)) }

and in a last ditch attempt:

my ($rin, $rout, $ein, $eout) = ('', '', '', ''); vec($rin, $chan, 1) = 1; # tried $ssh2 and $chan, # but should be: fileno($var) which errors if (select($rout=$rin, undef, $eout=$ein, 5000)) { print $buf while defined ($len = $chan->read($buf,512)) }

Neither above command set fixed the issue.

QUESTION

I'm now thinking it may be due to blocking(0); however, if that isnt' used, Windows just hangs after the connect. How can I get Net::SSH2 to "wait" until all output from a command is printed without just adding an artificially long sleep? Note that I use Net::Telnet to connect to linux and Cisco routers and this isn't an issue - it patiently waits until all output is printed and never seems to chop command output (which I think is the buffering issue).

SCRIPT

#!/usr/bin/perl use strict; use warnings; use Net::SSH2; use Term::ReadLine; my $host = '10.254.254.1'; my $user = 'cisco'; my $pass = 'cisco'; my ($len, $buf); my $ssh2 = Net::SSH2->new(); if ($ssh2->connect($host)) { print "[Connected]\n"; if ($ssh2->auth_password($user, $pass)) { print "[Authenticated]\n"; # Set up shell for interactive my $chan = $ssh2->channel(); $chan->blocking(0); # Needed on Windows $chan->shell(); #binmode($chan); # Seems to take care of both Unix SSH and Cis +co servers # Sometimes needed to "clear", flush() doesn't seem to work # Otherwise, first command does not work select(undef,undef,undef,0.2); # or sleep 1 print $buf while defined ($len = $chan->read($buf,512)); # START: Interactive Mode my $prompt = "ssh> "; my $ssh = Term::ReadLine->new("SSH"); $ssh->ornaments(0); while (defined (my $cmd = $ssh->readline($prompt))) { chomp $cmd; if ($cmd =~ /^\s*$/) { next } # nothing if ($cmd =~ /^\s*exit\s*$/) { last } # exit $chan->write("$cmd\n"); select(undef,undef,undef,0.2); # or sleep 1 print $buf while defined ($len = $chan->read($buf,512)) } # END: Interactive Mode $chan->close } else { warn "Authentication failed\n" } } else { warn "Unable to connect to host $host: $!\n" }

Replies are listed 'Best First'.
Re: Net::SSH2 command output polling
by t_rex_joe (Sexton) on Oct 12, 2012 at 18:39 UTC
    Vins,
    
    Honestly I've always used "Net::SSH::Perl" module.
    
    Found the interaction with TTY/PTYs to be less
    troublesome. Not to mention retrieving large
    amounts of "sh arp" and "sh cam dyn" from IOS/CATOS 
    devices.
    
    Here's a sample I use from my script for logging in.
    
    ###############
    
    
      $ssh = Net::SSH::Expect->new(
              host => $ip, 
              user => $suser,
              password => $spass,
              timeout => 3,
              log_file => "$ofdir/$stime.$bname.$ip.dump_out.$^T.txt",
              raw_pty => 1);
    
      $ok = $ssh->run_ssh();
      if(!(defined $ok) || ($ok eq "")) { $ok = 0; } else { $ok = 1; }
    
      if($debug == 1) { print "$meth... "; }
      $ok = $ssh->waitfor('key verification failed', 5);
      if((!$ok) || ($ok eq "")) { $ok = 0; }
      if($ok == 1) { if($debug == 1 ) { print "FOUND ERROR ID KEY FAILED\n"; } $fpssh = 1; }
       else { if($debug == 1) { print "DID NOT FIND.. CONTINUE\n"; } $fpssh = 0; } $ok = 0;
    
      if($fpssh == 1)
        {
        if($debug == 1) { print "FOUND host key failed PROMPT\n"; }
    
        $val = "host_key_failed_prompt";
    
        $ssh->close();
        exit;
        } #EO if($fpssh == 1)
    
    
    
        $meth = "FIND #";
        if($debug == 1) { print "$meth... "; }
        $ok = $ssh->waitfor('#', 30, -re);
        if((!$ok) || ($ok eq "")) { $ok = 0; }
        if($ok == 1) { if($debug == 1) { print "GO\n"; } $fppt = 1; }
         else { if($debug == 1) { print "ERROR\n"; } $fppt = 0; } $ok = 0;
    
        if($fppt != 1)
          {
          $val = "did_not_find_en_prompt";
    
    
          $ssh->close();
          exit;
          } #EO if($fpps == 1)
    
        $meth = "SEND SH ARP";
        if($debug == 1) { print "$meth... \n"; }
        $ssh->send($cmd);
        if($debug == 1) { print "GO\n"; }
        while(defined ($line = $ssh->read_line()))
          {
          if(!(defined $line) || ($line eq "")) { next; }
          if($debug == 1) { print "LINE: \"" . __LINE__ . "\" LINE: \"$line\"\n"; }
          push @outs, $line;
          }
    
        $ssh->send("\n\n");
    ##############
    
    To show the current buffer from the script incase
    of problems using "expect" like interactions..
    
    
        if($debug == 1) { print "PEEK: \"" . $ssh->peek(0) . "\" L: \"" . __LINE__ . "\"\n"; }
    
    -- Notice the line "while(defined ($line = $ssh->read_line()))". 
    This is different from the original line in the
    documentation of "@outs = $ssh->waitfor('#', -re, 30);"
    the "read line" is more fault tolerant.
    
    
    -- I make sure each module (login, run command, exit)
    is their own section of code, having nested if statements
    is bad if your are using "parallel fork manager" and if 
    you have to exit/next in the middle of the script.
    
    
    
    Joe