【问题标题】:Get output from a Paramiko SSH exec_command continuously从 Paramiko SSH exec_command 连续获取输出
【发布时间】:2015-10-28 08:39:57
【问题描述】:

我正在使用 paramiko 在远程机器上通过 ssh 执行一个长时间运行的 python 脚本。像魅力一样工作,到目前为止没有问题。

不幸的是,stdout(分别为stderr)仅在脚本完成后才会显示!但是,由于执行时间的原因,我更希望在打印时输出每个新行,而不是之后。

remote = paramiko.SSHClient()
remote.set_missing_host_key_policy(paramiko.AutoAddPolicy())
remote.connect("host", username="uname", password="pwd")
 
# myScript produces continuous output, that I want to capture as it appears    
stdin, stdout, stderr = remote.exec_command("python myScript.py")
stdin.close()
for line in stdout.read().splitlines():
    print(line)

如何做到这一点? 注意:当然可以将输出通过管道传输到一个文件并通过另一个 ssh 会话“减少”该文件,但这非常难看,我需要一个更干净,理想的pythonic解决方案:)

【问题讨论】:

  • 只是为了以后帮助别人,myScript.py应该包含sys.stdout.flush()

标签: python ssh stdout paramiko interactive


【解决方案1】:

如何使用this answer 的最小且完整的工作示例(在 Python 3.6.1 中测试)

# run.py
from paramiko import SSHClient

ssh = SSHClient()
ssh.load_system_host_keys()

ssh.connect('...')

print('started...')
stdin, stdout, stderr = ssh.exec_command('python -m example', get_pty=True)

for line in iter(stdout.readline, ""):
    print(line, end="")
print('finished.')

# example.py, at the server
import time

for x in range(10):
    print(x)
    time.sleep(2)

在本地机器上运行

python -m run

【讨论】:

    【解决方案2】:

    read([size]) documentation 中所指定,如果您未指定size,它会一直读取到EOF,这使得脚本等到命令结束后再从read() 返回并打印任何输出。

    查看以下答案:How to loop until EOF in Python?How to do a "While not EOF" 以获取有关如何耗尽类文件对象的示例。

    【讨论】:

    • 感谢@KurzedMetal 为我指明了正确的方向:for line in iter(lambda: stdout.readline(2048), ""): print(line, end="") ... 成功了!
    【解决方案3】:

    我遇到了类似的问题。我可以通过将 get_pty=True 添加到 paramiko 来解决它:

    stdin, stdout, stderr = client.exec_command("/var/mylongscript.py", get_pty=True)
    

    【讨论】:

    【解决方案4】:

    使用这个:

    stdin, stdout, stderr = ssh.exec_command('python -m example', get_pty=True)
    
    for line in iter(stdout.readline, ""):
        print(line, end="")
    

    来自@JorgeLeitao 的回答使我的标准输出几乎达到实时!

    我正在使用:

    stdin, stdout, stderr = ssh.exec_command(cmd)
    for line in stdout:
        # Process each line in the remote output
            print (line)
    

    【讨论】:

      【解决方案5】:

      来自生成器函数的流式响应数据。

      我想创建一个比标准 Client.exec_command() 示例更复杂且比 Channel.exec_command() 示例更复杂的类。另外,我还介绍了我遇到的一些“陷阱”。此摘要脚本已在 CentOS Stream - Python 3.6.8 上测试。

      import sys
      import paramiko
      
      client = paramiko.SSHClient()
      client.load_system_host_keys('/etc/ssh/ssh_known_hosts')
      
      try:
          client.connect('host', username='username', password='password',
              port=22, timeout=2)
      except Exception as _e:
          sys.stdout.write(_e)
      
      # is_active can be a false positive, so further test
      transport = client.get_transport()
      if transport.is_active():
          try:
              transport.send_ignore()
          except Exception as _e:
              sys.stdout.write(_e)
              sys.exit(1)
      else:
         sys.exit(1)
      
      channel = transport.open_session()
      # We're not handling stdout & stderr separately
      channel.set_combine_stderr(1)
      channel.exec_command('whoami')
      # Command was sent, no longer need stdin
      channel.shutdown_write()
      
      def responseGen(channel):
          # Small outputs (i.e. 'whoami') can end up running too quickly
          # so we yield channel.recv in both scenarios
          while True:
              if channel.recv_ready():
                  yield channel.recv(4096).decode('utf-8')
      
              if channel.exit_status_ready():
                  yield channel.recv(4096).decode('utf-8')
                  break
      
      # iterate over each yield as it is given 
      for response in responseGen(channel):
          sys.stdout.write(response)
      
      # We're done, explicitly close the conenction
      client.close()
      

      【讨论】: