【问题标题】:Long-running ssh commands in python paramiko module (and how to end them)python paramiko 模块中长时间运行的 ssh 命令(以及如何结束它们)
【发布时间】:2009-04-17 15:51:20
【问题描述】:

我想使用 python 的 paramiko 模块在远程机器上运行tail -f logfile 命令。到目前为止,我一直在尝试以下方式:

interface = paramiko.SSHClient()
#snip the connection setup portion
stdin, stdout, stderr = interface.exec_command("tail -f logfile")
#snip into threaded loop
print stdout.readline()

我希望命令在必要时运行,但我有 2 个问题:

  1. 如何彻底停止这种情况?我想创建一个频道,然后在我完成它时在频道上使用shutdown() 命令——但这似乎很混乱。是否可以将Ctrl-C 发送到频道的标准输入?
  2. readline() 块,如果我有一种非阻塞的方法来获取输出,我可以避免线程 - 有什么想法吗?

【问题讨论】:

  • 讨厌坏消息,但是 SSHClient() 已经在内部使用了线程。

标签: python ssh paramiko


【解决方案1】:

不要在客户端调用 exec_command,而是获取传输并生成您自己的通道。 channel可以用来执行一个命令,你可以在select语句中使用它来判断什么时候可以读取数据:

#!/usr/bin/env python
import paramiko
import select
client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('host.example.com')
transport = client.get_transport()
channel = transport.open_session()
channel.exec_command("tail -f /var/log/everything/current")
while True:
  rl, wl, xl = select.select([channel],[],[],0.0)
  if len(rl) > 0:
      # Must be stdout
      print channel.recv(1024)

通道对象可以读写,与远程命令的stdout和stdin连接。你可以通过调用channel.makefile_stderr(...)来访问stderr。

我已将超时设置为 0.0 秒,因为请求了非阻塞解决方案。根据您的需要,您可能希望使用非零超时来阻止。

【讨论】:

  • 你不能在 stdout 对象上选择,因为它缺少 fileno 属性。 goathens 没有使用通道对象。
  • 我已经修改并扩展了示例,并对其进行了测试以确保它可以正常工作:)。
  • @Vivek:您仍然需要查看rl,这是可以读取的套接字列表。查看channel.recv_stderr()(和channel.recv_stderr_ready())的文档,了解如何读取远程标准错误。
  • 我明白了,谢谢,我正在尝试使用 xl,但结果很奇怪。
  • 这个脚本仍然会占用我 100% 的 CPU 来执行一个遥远的 sleep 10
【解决方案2】:

1) 如果您愿意,您可以关闭客户端。另一端的服务器将终止尾部进程。

2) 如果您需要以非阻塞方式执行此操作,则必须直接使用通道对象。然后,您可以使用 channel.recv_ready() 和 channel.recv_stderr_ready() 来查看 stdout 和 stderr,或者使用 select.select。

【讨论】:

  • 我迟到了,但exec_command 本身不是没有阻塞的吗?
  • 在一些较新的服务器上,即使您终止客户端,您的进程也不会被杀死。您必须在exec_command() 中设置get_pty=True,以便在退出客户端后清理进程。
【解决方案3】:

Andrew Aylett 对解决方案的小幅更新。以下代码实际上是在外部进程完成时中断循环并退出:

import paramiko
import select

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('host.example.com')
channel = client.get_transport().open_session()
channel.exec_command("tail -f /var/log/everything/current")
while True:
    if channel.exit_status_ready():
        break
    rl, wl, xl = select.select([channel], [], [], 0.0)
    if len(rl) > 0:
        print channel.recv(1024)

【讨论】:

  • @azmeuk 两种解决方案都有点不正确,因为您不想在退出状态就绪后立即停止接收输出。当没有要接收的输出并且退出状态已准备好时,您想停止。否则你可能会在收到所有输出之前退出。
  • @Jiri,你是对的,我面临着你提到的类似问题。如果有任何解决方法,请告诉我。我的一些输出是从 tail -f . 中跳过的
【解决方案4】:

要关闭进程,只需运行:

interface.close()

就非阻塞而言,您无法获得非阻塞读取。您能做的最好的事情是一次解析一个“块”,“stdout.read(1)”只有在缓冲区中没有字符时才会阻塞。

【讨论】:

    【解决方案5】:

    仅供参考,有一个使用 channel.get_pty() 的解决方案。更多详情请看:https://stackoverflow.com/a/11190727/1480181

    【讨论】:

      【解决方案6】:

      我解决这个问题的方法是使用上下文管理器。这将确保我长时间运行的命令被中止。关键逻辑是包装以模仿 SSHClient.exec_command 但捕获创建的通道并使用Timer 如果命令运行时间过长将关闭该通道。

      import paramiko
      import threading
      
      
      class TimeoutChannel:
      
          def __init__(self, client: paramiko.SSHClient, timeout):
              self.expired = False
              self._channel: paramiko.channel = None
              self.client = client
              self.timeout = timeout
      
          def __enter__(self):
              self.timer = threading.Timer(self.timeout, self.kill_client)
              self.timer.start()
      
              return self
      
          def __exit__(self, exc_type, exc_val, exc_tb):
              print("Exited Timeout. Timed out:", self.expired)
              self.timer.cancel()
      
              if exc_val:
                  return False  # Make sure the exceptions are re-raised
      
              if self.expired:
                  raise TimeoutError("Command timed out")
      
          def kill_client(self):
              self.expired = True
              print("Should kill client")
              if self._channel:
                  print("We have a channel")
                  self._channel.close()
      
          def exec(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None):
              self._channel = self.client.get_transport().open_session(timeout=timeout)
              if get_pty:
                  self._channel.get_pty()
              self._channel.settimeout(timeout)
              if environment:
                  self._channel.update_environment(environment)
              self._channel.exec_command(command)
              stdin = self._channel.makefile_stdin("wb", bufsize)
              stdout = self._channel.makefile("r", bufsize)
              stderr = self._channel.makefile_stderr("r", bufsize)
              return stdin, stdout, stderr
      

      现在使用代码非常简单,第一个示例将抛出 TimeoutError

      ssh = paramiko.SSHClient()
      ssh.connect('hostname', username='user', password='pass')
      
      with TimeoutChannel(ssh, 3) as c:
          ssh_stdin, ssh_stdout, ssh_stderr = c.exec("cat")    # non-blocking
          exit_status = ssh_stdout.channel.recv_exit_status()  # block til done, will never complete because cat wants input
      

      这段代码可以正常工作(除非主机处于疯狂的负载下!)

      ssh = paramiko.SSHClient()
      ssh.connect('hostname', username='user', password='pass')
      
      with TimeoutChannel(ssh, 3) as c:
          ssh_stdin, ssh_stdout, ssh_stderr = c.exec("uptime")    # non-blocking
          exit_status = ssh_stdout.channel.recv_exit_status()     # block til done, will complete quickly
          print(ssh_stdout.read().decode("utf8"))                 # Show results
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-04
        • 1970-01-01
        • 2016-07-08
        • 1970-01-01
        相关资源
        最近更新 更多