7

I'm using Paramiko to issue a number of commands and collect results for further analysis. Every once in a while the results from the first command are note fully returned in time and end up in the output for the second command.

I'm attempting to use recv_ready to account for this but it is not working so I assume I am doing something wrong. Here's the relevant code:

pause = 1

def issue_command(chan, pause, cmd):
    # send commands and return results
    chan.send(cmd + '\n')
    while not chan.recv_ready():
        time.sleep(pause)
    data = chan.recv(99999)

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
chan = ssh.connect(host, port=22, username=username, password=password, timeout=3,)

resp1 = issue_command(chan, pause, cmd1)
resp2 = issue_command(chan, pause, cmd2)

The output for these commands is relatively small (a few sentences). Increasing the pause would likely solve the problem but is not an ideal solution.

Any suggestions or recommendations would be appreciated.

KMS
  • 83
  • 1
  • 1
  • 4

1 Answers1

11

I would use transport directly and create a new channel for each command. Then you can use something like:

def issue_command(transport, pause, command):
    chan = transport.open_session()
    chan.exec_command(command)

    buff_size = 1024
    stdout = ""
    stderr = ""

    while not chan.exit_status_ready():
        time.sleep(pause)
        if chan.recv_ready():
            stdout += chan.recv(buff_size)

        if chan.recv_stderr_ready():
            stderr += chan.recv_stderr(buff_size)

    exit_status = chan.recv_exit_status()
    # Need to gobble up any remaining output after program terminates...
    while chan.recv_ready():
        stdout += chan.recv(buff_size)

    while chan.recv_stderr_ready():
        stderr += chan.recv_stderr(buff_size)

    return exit_status, stdout, stderr

ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(host, port=22, username=username, password=password, timeout=3,)
transport = ssh.get_transport()
pause = 1    

resp1 = issue_command(transport, pause, cmd1)
resp2 = issue_command(transport, pause, cmd2)

An even better way would be to take a list of commands and spawn a new channel for each, poll each chan's recv_ready, and suck up their stdout/stderr when output is available. :-)

Edit: There are potential issues with reading data after the command exits. Please see the comments!

ferrouswheel
  • 3,341
  • 1
  • 22
  • 24
  • I think this got me on the right track. Thank you. I needed to create a new connection and transport for each command issued so I moved the ssh.connect and transport statements into the issue_command function. Results have been good so far. – KMS Jan 23 '14 at 02:55
  • 2
    Note that this solution can work but is not bullet-proof - it can still exit before all the data has been received, since `recv_ready()` can return `False` before the channel stream has closed. More details: http://stackoverflow.com/a/39019370/2075565 – Ivan Aug 18 '16 at 13:22
  • There's a race condition in your code: there may still be data on stdout or stderr buffers after the command has exited and you've retrieved the exit status. Please see: http://stackoverflow.com/a/39918539/615987 – Patrick Oct 07 '16 at 13:35