Paramiko频道在读取大输出时卡住了

时间:2013-02-01 10:07:10

标签: 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_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()

上面的代码适用于小输出,但它会因较大的输出而卡住。

这里有缓冲相关的问题吗?

6 个答案:

答案 0 :(得分:7)

我发布了最终代码,该代码使用了Bruce Wayne(:))

的输入
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 the given command
    chan.exec_command(cmd)

    # To capture Data. Need to read the entire buffer to capture output
    contents = StringIO.StringIO()
    error = StringIO.StringIO()

    while not chan.exit_status_ready():
        if chan.recv_ready():
            data = chan.recv(1024)
            #print "Indside stdout"
            while data:
                contents.write(data)
                data = chan.recv(1024)

        if chan.recv_stderr_ready():            
            error_buff = chan.recv_stderr(1024)
            while error_buff:
                error.write(error_buff)
                error_buff = chan.recv_stderr(1024)

    exit_status = chan.recv_exit_status()

except socket.timeout:
    raise socket.timeout

output = contents.getvalue()
error_value = error.getvalue()

return output, error_value, exit_status

答案 1 :(得分:3)

我发现没有与stdout频道有关的问题,但我不确定你处理stderr的方式。你能否证实,它不是捕获导致问题的stderr? 我会尝试你的代码并告诉你。

更新: 当您执行的命令在STDERR中提供大量消息时,您的代码会冻结。我不确定为什么,但recv_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_stderr(600)更改为recv_stderr(1024)或更高。

答案 2 :(得分:2)

实际上我认为以上所有答案都无法解决真正的问题:

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

stdout.readlines()
stderr.readlines()

会永远挂起来。虽然

stderr.readlines()
stdout.readlines()

会解决这种情况,但如果远程程序首先产生大量的标准输出,它将会失败。

我还没有解决方案......

答案 3 :(得分:0)

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

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

如果您之后收到其他数据,则需要再次从这些文件句柄中读取。

答案 4 :(得分:0)

要使paramiko命令的行为类似于subprocess.call,您可以使用这段代码(使用python-3.5和paramiko-2.1.1测试):

#!/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')                                                           

答案 5 :(得分:0)

TL; DR:如果使用stdout.readlines(),则在stderr.readlines()之前致电ssh.exec_command()

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

sh = 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)

您可能想知道大输出可能带来的限制。

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

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

我之所以发布此帖子,是因为我很笨,花了很长时间,试图弄清楚我的代码是如何破解的,答案是我从stderr之前stdout开始阅读},而我的stdout太大,无法容纳在缓冲区中。