Python 读取大输出时,Paramiko通道阻塞

Python 读取大输出时,Paramiko通道阻塞,python,paramiko,Python,Paramiko,我有一段代码,在远程Linux机器上执行命令,并使用Paramiko读取输出。代码def如下所示: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(IPAddress, username=user['username'], password=user['password']) chan = self.ssh.get_transport().open

我有一段代码,在远程Linux机器上执行命令,并使用Paramiko读取输出。代码def如下所示:

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(IPAddress, username=user['username'], password=user['password'])


chan = self.ssh.get_transport().open_session()

chan.settimeout(10800)

try:
    # Execute thecommand
    chan.exec_command(cmd)

    contents = StringIO.StringIO()

    data = chan.recv(1024)

    # Capturing data from chan buffer.
    while data:
        contents.write(data)
        data = chan.recv(1024)

except socket.timeout:
    raise socket.timeout


output = contents.getvalue()

return output,chan.recv_stderr(600),chan.recv_exit_status()
上面的代码适用于较小的输出,但对于较大的输出,它会被卡住


这里是否存在与缓冲区相关的问题

我认为stdout频道没有问题,但我不确定您处理stderr的方式。你能确认一下,这不是导致问题的原因吗? 我将试用你的代码并让你知道

更新: 当您执行的命令在STDERR中给出大量消息时,您的代码将冻结。我不知道为什么,但原因可能是
recv\u stderr(600)
。 所以,捕获错误流的方法与捕获标准输出的方法相同。 大概

contents_err = StringIO.StringIO()

data_err = chan.recv_stderr(1024)
while data_err:
    contents_err.write(data_err)
    data_err = chan.recv_stderr(1024)

您甚至可以先尝试将
recv\u stderr(600)
更改为
recv\u stderr(1024)
或更高版本。

如果使用开放ssh会话的高级表示,则更容易。因为您已经使用打开频道,所以可以从那里运行命令,避免额外的工作

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(IPAddress, username=user['username'], password=user['password'])

stdin, stdout, stderr = ssh.exec_command(cmd)
for line in stdout.readlines():
    print line
for line in stderr.readlines():
    print line

如果之后收到其他数据,则需要返回并再次读取这些文件句柄

我正在发布最终代码,该代码使用了Bruce Wayne(:)的输入


事实上,我认为以上所有答案都不能解决真正的问题:

如果远程程序首先产生大量的stderr输出,则

stdout.readlines()
stderr.readlines()
他将永远被绞死。虽然

stderr.readlines()
stdout.readlines()
将解决此问题,但如果远程程序首先生成大量的标准输出,则会失败


我还没有解决办法

要使paramiko命令的行为类似于子进程。调用,您可以使用以下代码段(使用python-3.5和paramiko-2.1.1进行测试):


TL;DR:如果使用
ssh.exec\u命令()

如果您使用@Spencer Rathbun的答案:

sh=paramiko.SSHClient()
ssh.set_缺少_主机_密钥_策略(paramiko.AutoAddPolicy())
ssh.connect(IPAddress,username=user['username'],password=user['password'])
stdin,stdout,stderr=ssh.exec_命令(cmd)
您可能希望了解由于具有大型输出而可能产生的限制

根据实验,
stdin、stdout、stderr=ssh.exec_命令(cmd)
将无法立即将完整输出写入
stdout
stderr
。更具体地说,缓冲区在填充之前似乎包含
2^21
(2097152)个字符。如果任何缓冲区已满,
exec_command
将在写入该缓冲区时阻塞,并将保持阻塞状态,直到该缓冲区清空到足以继续。这意味着如果您的
stdout
太大,您将挂起读取
stderr
,因为在两个缓冲区中都无法接收EOF,直到它可以写入完整的输出

解决此问题的简单方法是Spencer使用的方法-在尝试读取
stderr
之前,通过
stdout.readlines()
获取所有正常输出。只有当
stderr
中的字符数超过
2^21
时,此操作才会失败,这在我的用例中是一个可接受的限制


我之所以发布这篇文章,主要是因为我很笨,花了太多太长的时间试图找出我是如何破坏代码的,而答案是我在
stdout
之前阅读
stdout
,而我的
stdout
太大,无法放入缓冲区。

我为stdout运行了代码。哇,成功了。但它不适用于stderr。实际上,我需要使用传输会话,因为我需要检查cmds的返回代码。@SpencerRathbun在使用stdout.readlines()之前,您不必检查stdout.channel.recv_ready()吗?看起来有时候readlines()在stdout上确实应该有东西的时候却什么也不返回。@DavidDoria根据我对文档的阅读,在命令运行并且流返回后,我们得到了一个类似文件的对象。我们不需要检查,因为一切都完成了。如果这是不正确的,我肯定会感到惊讶。这假设只要exit_status_ready()为真,recv_ready()就为真,对吗?我见过这样的情况:exit_status_ready()为true,但recv_ready()尚未为true,即使它最终有输出(即,如果我在读取前用'while not stdout.channel.recv_ready()手动阻止它):pass'。这种方法的问题是,如果没有任何数据,它就会陷入无限等待循环。有更好的方法吗?是的,我遇到了同样的问题。所以我所做的是在while循环之前检查chan。exit_status_ready()。我不明白-您只需检查exit_status_ready()循环前一次?这如何确定recv_ready()是否会返回?请发布代码片段好吗?ohh好的。我没有尝试你的方案。我只是检查了我的代码。我实际上从未检查recv_ready()。我只是检查chan.exit_status_ready(),就是这样。我的代码工作正常。小心,
chan.exit_status_ready()
表示远程进程已完成,但不是您已完成读取输入缓冲区。也就是说,如果您的代码运行缓慢(或者您只需输入一个时间),则可能会导致没有数据。在循环之前进行睡眠(5)由于通道发出远程进程完成的信号,而您刚刚停止读取。我建议检查以下内容:
chan.exit_status_ready()、not recv_ready()和not recv_stderr_ready()
,查看循环条件,然后是chan.read()最后从缓冲区中获取任何剩余。第一次或第二次生成的输出实际上并不重要,如果发送到缓冲区的字符总数超过
2^21
个,这些输出仍将挂起等待EOF。我也没有很好的解决方案-您可以使用
stdout.channel.recv\ready()
以确定s中是否有任何字节
stderr.readlines()
stdout.readlines()
#!/usr/bin/env /usr/bin/python3                                                

import os                                                                  
import sys                                                                                                                    
from paramiko import SSHClient, AutoAddPolicy               
from socket import getfqdn                                       

class SecureSHell(object):                                                 
    reuser = os.environ['USER']                                            
    remote = ''                                                            
    def __init__(self, *args, **kwargs):                                   
        for arg in args:                                                   
            if hasattr(self, arg):                                         
                setattr(self, arg, True)                                   
        for (key, val) in kwargs.items():                                  
            if hasattr(self, key):                                         
                setattr(self, key, val)

    @staticmethod                                                          
    def _ssh_(remote, reuser, port=22):                                    
        if '@' in remote:                                                  
            _reuser, remote = remote.split('@')                            
        _fqdn = getfqdn(remote)                                            
        remote = _fqdn if _fqdn else remote                                
        ssh = SSHClient()                                                  
        ssh.set_missing_host_key_policy(AutoAddPolicy()) 
        ssh.connect(remote, int(port), username=reuser)                                                                     
        return ssh                                                         

    def call(self, cmd, remote=None, reuser=None):                         
        remote = remote if remote else self.remote                         
        reuser = reuser if reuser else self.reuser              
        ssh = self._ssh_(remote, reuser)                                   
        chn = ssh.get_transport().open_session()                           
        chn.settimeout(10800)                                              
        chn.exec_command(cmd)                                              
        while not chn.exit_status_ready():                                 
            if chn.recv_ready():                                           
                och = chn.recv(1024)                                       
                while och:                                                 
                    sys.stdout.write(och.decode())                         
                    och = chn.recv(1024)                                   
            if chn.recv_stderr_ready():                                    
                ech = chn.recv_stderr(1024)                                
                while ech:                                                 
                    sys.stderr.write(och.decode())                         
                    ech = chn.recv_stderr(1024)                            
        return int(chn.recv_exit_status())                                 

ssh = SecureSHell(remote='example.com', user='d0n')                       
ssh.call('find')