Capturing standard out from a Paramiko command

时光毁灭记忆、已成空白 提交于 2019-12-05 19:04:04

I encountered the same problem. The problem is that after the command exited there may still be data on the stout or stderr buffers, still on its way over the network, or whatever else. I read through paramiko's source code and apparently all data's been read once chan.recv() returns empty string.

So this is my attempt to solve it, until now it's been working.

def run_cmd(ssh, cmd, stdin=None, timeout=-1, recv_win_size=1024):
    '''
    Run command on server, optionally sending data to its stdin

    Arguments:
        ssh           -- An instance of paramiko.SSHClient connected
                         to the server the commands are to be executed on
        cmd           -- The command to run on the remote server
        stdin         -- String to write to command's standard input
        timeout       -- Timeout for command completion in seconds.
                         Set to None to make the execution blocking.
        recv_win_size -- Size of chunks the output is read in

    Returns:
        A tuple containing (exit_status, stdout, stderr)
    '''

    with closing(ssh.get_transport().open_session()) as chan:
        chan.settimeout(timeout)
        chan.exec_command(cmd)
        if stdin:
            chan.sendall(stdin)
            chan.shutdown_write()

        stdout, stderr = [], []

        # Until the command exits, read from its stdout and stderr
        while not chan.exit_status_ready():
            if chan.recv_ready():
                stdout.append(chan.recv(recv_win_size))
            if chan.recv_stderr_ready():
                stderr.append(chan.recv_stderr(recv_win_size))

        # Command has finished, read exit status
        exit_status = chan.recv_exit_status()

        # Ensure we gobble up all remaining data
        while True:
            try:
                sout_recvd = chan.recv(recv_win_size)
                if not sout_recvd and not chan.recv_ready():
                    break
                else:
                    stdout.append(sout_recvd)
            except socket.timeout:
                continue

        while True:
            try:
                serr_recvd = chan.recv_stderr(recv_win_size)
                if not serr_recvd and not chan.recv_stderr_ready():
                    break
                else:
                    stderr.append(serr_recvd)
            except socket.timeout:
                continue

    stdout = ''.join(stdout)
    stderr = ''.join(stderr)

    return (exit_status, stdout, stderr)
Ivan

I encountered the same issue.

This link (Paramiko: how to ensure data is received between commands) gave me some help, in explaining that after you get exit_status_ready() you still have to receive possible additional data. In my tests (with a couple of screens of output), in every single run, there will be additional data to read after exit_status_ready() returns True.

But the way it reads the remaining data it is not correct: it uses recv_ready() to check if there is something to read, and once recv_ready() returns False it exits. Now, it will work most of the time. But the following situation can happen: recv_ready() can return False to indicate that at that moment there is nothing to receive, but it doesn't mean that it is the end of the all data. In my tests, I would leave the test running, and sometimes it would take half an hour for the issue to appear.

I found the solution by reading the following sentence in the Channel.recv() documentation: "If a string of length zero is returned, the channel stream has closed."

So we just can have a single loop and read all the data until recv() returns zero length result. At that point channel stream is closed, but just to make sure that exit status is ready we can make additional loop and sleep until channel.exit_status_ready() returns True.

Note that this will work only with a channel without pty enabled (which is default).

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!