当我从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上的所有数据?
长话短说:我遇到了大部分这些问题,并提出了我的大多数ssh相关问题的最终解决方案。因此,请随时查看this exec_command
的实现,避免大多数empty_response / stalling场景。
很长的故事
这里的主要问题是你exec_command()
是非阻塞的,并产生一个负责通道通信的线程。此线程正在等待传入数据并将其放入通道缓冲区,而主线程可能会继续。这就是你的问题所在。即使在你检查到你的一方收到了exit_status
之前,你太早读了你的缓冲区。接收exit_status
确认远程进程退出给定的状态代码。接收远程状态代码并不表示您已收到可能仍在传输的所有数据。它甚至可能在您身边无序到达,并说即使在收到所有数据(status_code
,stderr
)之前,stdout
也可能会到达。
数据到达时,主线程仍在继续。在你的情况下,主线程尝试从stdout
和stderr
读取一次,然后阻止,直到exit_status
准备好。请注意,通道线程仍可能从远程命令调用接收数据。另请注意,一旦您收到远程exit_status
,您必须手动清空缓冲区。
在您的具体情况下,这是发生的事情:
execute_command
生成一个新线程(让我们调用id exec_thread,管理远程命令调用stdout
,stderr
存储self.stdout
,self.stderr
缓冲区的当前内容。请注意,您不知道您是否收到了stderr
或stdout
的所有数据。很可能你刚收到一些块。stdout
,stderr
的数据,而[main-thread]继续。exit_status
。再次注意,此时你的缓冲区可能仍然满了最近收到的stderr
,stdout
块。exit_status
存储在self.exit_code
中。 [exec_thread]仍然存在,可能仍会收到一些乱序数据。输入缓冲区可能仍然被填充。run_cmd
返回请注意,该通道的[exec_thread] - paramiko为每个通道调用创建一个线程(即exec_command
) - 仍处于活动状态,并且它们将总结空闲并创建问题,直到您手动关闭它们(stdout.channel.close()
)。