簡體   English   中英

讀取大輸出時,Paramiko 通道卡住

[英]Paramiko channel stucks when reading large ouput

我有一個代碼,我在遠程 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()

上面的代碼適用於小輸出,但它會卡住更大的輸出。

這里有任何與緩沖區相關的問題嗎?

我正在發布與布魯斯·韋恩 (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

我認為沒有與 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)或更高版本。

其實我認為以上所有答案都不能解決真正的問題:

如果遠程程序首先產生大量的stderr 輸出,那么

stdout.readlines()
stderr.readlines()

會永遠掛着。 雖然

stderr.readlines()
stdout.readlines()

將解決這種情況,但如果遠程程序首先產生大量stdout 輸出,它將失敗。

我還沒有解決辦法...

如果您使用開放 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

如果您之后收到其他數據,您將需要返回並再次讀取這些文件句柄。

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 使用的方法 - 在嘗試讀取stderr之前通過stdout.readlines()獲取所有正常輸出。 只有當stderr有超過2^21字符時,這才會失敗,這在我的用例中是可以接受的限制。

我發布這個主要是因為我很笨,花了很長時間試圖弄清楚我是如何破壞我的代碼的,當答案是我在stdout之前從stderr讀取並且我的stdout太大而無法放入緩沖。

要讓 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')                                                           

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM