stramr上的Paramiko recv()/ read()/ readline(s)()返回空字符串

问题描述 投票:1回答:1

当我从read()频道阅读(readline() / readlines() / stderr)时,我正在使用paramiko收集远程主机上的一些信息并遇到问题。

有时stderr.read()返回一个空字符串,这对我来说看起来像是竞争条件的结果。但是,根据我在互联网上找到的文档和示例,这似乎是确切的方法。

我还尝试打开一个专用通道并利用chan.recv_ready() / chan.recv_stderr_ready()并通过chan.recv() / chan.recv_stderr()从循环中读取相应的通道,但这会导致相同的行为。

这是一个最小的测试用例 - 在我的设置中 - 可靠地导致该行为。

import paramiko

class SSH:
    def __init__(self):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ssh.connect('127.0.0.1', port=31337, username='root', password='root')

        self.stdout = ''
        self.stderr = ''
        self.exit_code = 0

    def _run_cmd(self, cmd):
        self.stdout = ''
        self.stderr = ''

        stdin, stdout, stderr = self.ssh.exec_command(cmd)

        self.stdout = stdout.read()
        self.stderr = stderr.read()

        while not stdout.channel.exit_status_ready():
            pass
        self.exit_code = stdout.channel.recv_exit_status()

        if self.exit_code:
            print("ERROR: " + self.stderr)

    def process_list(self):
        self._run_cmd('ls /proc/ | grep -E "^[0-9]+$" | grep -v $$')
        lines = self.stdout.split('\n')[:-1]
        data = []

        for process in lines:
            process_data = {}
            process_data['pid'] = int(process)

            # fetching and parsing process status information from /proc/[PID]/status

            self._run_cmd('cat /proc/%d/status' % (int(process)))

            data.append(self.stdout)

        return data


data = SSH()
while True:
    print data.process_list()

经过几次运行(如果不是第一次)后我得到的是: ERROR: ,而我期待: ERROR: cat: /proc/12883/status: No such file or directory

如何确保stderr准备好读取/我读取stderr上的所有数据?

python ssh paramiko stderr
1个回答
0
投票

长话短说:我遇到了大部分这些问题,并提出了我的大多数ssh相关问题的最终解决方案。因此,请随时查看this exec_command的实现,避免大多数empty_response / stalling场景。

很长的故事

这里的主要问题是你exec_command()是非阻塞的,并产生一个负责通道通信的线程。此线程正在等待传入数据并将其放入通道缓冲区,而主线程可能会继续。这就是你的问题所在。即使在你检查到你的一方收到了exit_status之前,你太早读了你的缓冲区。接收exit_status确认远程进程退出给定的状态代码。接收远程状态代码并不表示您已收到可能仍在传输的所有数据。它甚至可能在您身边无序到达,并说即使在收到所有数据(status_codestderr)之前,stdout也可能会到达。

数据到达时,主线程仍在继续。在你的情况下,主线程尝试从stdoutstderr读取一次,然后阻止,直到exit_status准备好。请注意,通道线程仍可能从远程命令调用接收数据。另请注意,一旦您收到远程exit_status,您必须手动清空缓冲区。

在您的具体情况下,这是发生的事情:

  1. execute_command生成一个新线程(让我们调用id exec_thread,管理远程命令调用
  2. 虽然最近生成的线程可以接收数据,但主线程继续。
  3. [主线]你在stdoutstderr存储self.stdoutself.stderr缓冲区的当前内容。请注意,您不知道您是否收到了stderrstdout的所有数据。很可能你刚收到一些块。
  4. [exec_thread]愉快地接收stdoutstderr的数据,而[main-thread]继续。
  5. [main-thread] busy-blocks直到收到exit_status。再次注意,此时你的缓冲区可能仍然满了最近收到的stderrstdout块。
  6. [主线] exit_status存储在self.exit_code中。 [exec_thread]仍然存在,可能仍会收到一些乱序数据。输入缓冲区可能仍然被填充。
  7. [main-thread]从run_cmd返回

请注意,该通道的[exec_thread] - paramiko为每个通道调用创建一个线程(即exec_command) - 仍处于活动状态,并且它们将总结空闲并创建问题,直到您手动关闭它们(stdout.channel.close())。

© www.soinside.com 2019 - 2024. All rights reserved.