从Paramiko命令中捕获标准

时间:2015-06-23 12:35:24

标签: python paramiko

我有一个围绕Paramiko SSHClient.exec_command()的包装器。我想捕捉标准。这是我的函数的缩短版本:

def __execute(self, args, sudo=False, capture_stdout=True, plumb_stderr=True,
  ignore_returncode=False):

  argstr = ' '.join(pipes.quote(arg) for arg in args)

  channel = ssh.get_transport().open_session()
  channel.exec_command(argstr)

  channel.shutdown_write()

  # Handle stdout and stderr until the command terminates
  captured = []

  def do_capture():
    while channel.recv_ready():
      o = channel.recv(1024)
      if capture_stdout:
        captured.append(o)
      else:
        sys.stdout.write(o)
        sys.stdout.flush()

    while plumb_stderr and channel.recv_stderr_ready():
      sys.stderr.write(channel.recv_stderr(1024))
      sys.stderr.flush()

  while not channel.exit_status_ready():
    do_capture()

  # We get data after the exit status is available, why?
  for i in xrange(100):
    do_capture()

  rc = channel.recv_exit_status()
  if not ignore_returncode and rc != 0:
    raise Exception('Got return code %d executing %s' % (rc, args))

  if capture_stdout:
    return ''.join(captured)

paramiko.SSHClient.execute = __execute

do_capture()中,只要channel.recv_ready()告诉我可以从命令的标准输出中接收数据,我就会调用channel.recv(1024)并将数据附加到我的缓冲区。当命令的退出状态可用时我停止。

然而,在退出状态之后的某个时刻似乎会出现更多标准数据。

# We get data after the exit status is available, why?
for i in xrange(100):
  do_capture()

我不能只调用do_capture()一次,因为看起来channel.recv_ready()将返回False几毫秒,然后是True,接收到更多数据,然后再次返回False。

我正在使用Python 2.7.6和Paramiko 1.15.2。

2 个答案:

答案 0 :(得分:5)

我遇到了同样的问题。问题是退出命令后,stout或stderr缓冲区上仍可能存在数据,仍然在网络上或其他任何地方。我阅读了paramiko的源代码,显然所有数据都被读过一次chan.recv()返回空字符串。

所以这是我尝试解决它,直到现在它一直在工作。

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)

答案 1 :(得分:1)

我遇到了同样的问题。

此链接(Paramiko: how to ensure data is received between commands)给了我一些帮助,解释说在获得exit_status_ready()之后,您仍然需要获得可能的其他数据。在我的测试中(有几个输出屏幕),在每次运行中,exit_status_ready()返回True后都会有其他数据要读取。

但是它读取剩余数据的方式并不正确:它使用recv_ready()来检查是否有要读取的内容,并且一旦recv_ready()返回False,它就会退出。现在,它大部分时间都可以使用。但是可能发生以下情况:recv_ready()可以返回False以指示此时没有任何内容可以接收,但这并不意味着它是所有数据的结尾。在我的测试中,我会让测试继续运行,有时需要半小时才会出现问题。

我通过阅读Channel.recv()文档中的以下句子找到了解决方案:"If a string of length zero is returned, the channel stream has closed."

所以我们只需要一个循环并读取所有数据,直到recv()返回零长度结果。此时关闭了频道流,但为了确保退出状态已准备好,我们可以进行额外的循环和休眠,直到channel.exit_status_ready()返回True

请注意,这仅适用于未启用pty的频道(默认情况下)。