У меня есть код, где я выполняю команду на удаленной машине 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()

Приведенный выше код работает для небольших выходов, но он застревает для больших выходов.

Есть ли здесь проблема, связанная с буфером?

10
vipulb 1 Фев 2013 в 14:07

6 ответов

Лучший ответ

Я не вижу проблем, связанных с каналом 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) или выше.

3
bruce_w 11 Фев 2013 в 14:00

Чтобы команды 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')                                                           
0
d0n 15 Янв 2017 в 20:37

Я публикую окончательный код, который работал с входами Брюса Уэйна (:))

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
8
Delimitry 27 Июл 2016 в 15:31

На самом деле я думаю, что все приведенные выше ответы не могут решить реальную проблему:

Если удаленная программа сначала выдает большое количество вывода stderr , то

stdout.readlines()
stderr.readlines()

Висел бы навсегда. хотя

stderr.readlines()
stdout.readlines()

Разрешит этот случай, но не удастся, если удаленная программа сначала выдаст большое количество вывода stdout .

У меня пока нет решения ...

2
fubupc 13 Фев 2014 в 03:29

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) не сможет сразу записать полный вывод в stdout и stderr. Точнее говоря, в буфере содержится 2^21 (2 097 152) символов перед заполнением. Если какой-либо буфер заполнен, exec_command заблокирует запись в этот буфер и останется заблокированным до тех пор, пока этот буфер не будет достаточно очищен для продолжения. Это означает, что если ваш stdout слишком большой, вы будете зависать при чтении stderr, поскольку вы не получите EOF ни в одном буфере, пока он не сможет записать полный вывод.

Простой способ обойти это тот, который использует Спенсер - получить все нормальные выходные данные через stdout.readlines(), прежде чем пытаться прочитать stderr. Это не удастся, только если в stderr у вас будет больше 2^21 символов, что является приемлемым ограничением в моем случае использования.

Я в основном пишу это, потому что я тупой и потратил много времени, слишком долго пытаясь выяснить, как я сломал свой код, когда ответ был, что я читал с stderr до stdout и мой stdout был слишком большим, чтобы поместиться в буфер.

0
jeremysprofile 7 Янв 2020 в 21:00

Это проще, если вы используете высокоуровневое представление открытого сеанса SSH. Поскольку вы уже используете 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

Вам нужно будет вернуться и снова прочитать дескрипторы этих файлов, если впоследствии вы получите дополнительные данные.

1
Spencer Rathbun 11 Фев 2013 в 15:18