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.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
KMS
  • 83
  • 1
  • 1
  • 4

2 Answers2

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,658
  • 2
  • 24
  • 24
  • 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
0

I like the answer from @ferrouswheel. I've been using something similar to the solution that @KMS had, which is:

def wait_time():
    while True:
        if chan.recv_ready() == False:
            time.sleep(1)
            continue
        else:
            break

This can also be shortened to:

def wait_time():
    while chan.recv_ready() == False:
        time.sleep(1)

In either case, whenever needed, I'd simply place:

wait_time()

..inside the code.

hNomad
  • 11
  • 3