python paramiko模块中长时间运行的ssh命令(以及如何结束它们)

时间:2009-04-17 15:51:20

标签: python ssh paramiko

我想使用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()

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

  1. 如何干净利落地停止?我考虑制作一个频道,然后在我通过时使用频道上的shutdown()命令 - 但这看起来很混乱。是否可以执行类似已发送Ctrl-C到频道的stdin?
  2. 的操作
  3. readline()阻止,如果我有一个非阻塞的输出方法,我可以避免线程 - 任何想法?

6 个答案:

答案 0 :(得分:21)

不要在客户端上调用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秒,因为请求了非阻塞解决方案。根据您的需要,您可能希望使用非零超时进行阻止。

答案 1 :(得分:14)

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

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

答案 2 :(得分:8)

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)

答案 3 :(得分:0)

要关闭该过程,只需运行:

interface.close()

就非阻塞而言,您无法获得非阻塞读取。您最好能够一次解析一个“块”,“stdout.read(1)”将仅在缓冲区中没有剩余字符时阻止。

答案 4 :(得分:0)

仅供参考,有一个解决方案可以使用channel.get_pty()来完成此操作。更多细节请看:https://stackoverflow.com/a/11190727/1480181

答案 5 :(得分:0)

我解决此问题的方法是使用上下文管理器。这将确保我长时间运行的命令被中止。关键逻辑是包装为模仿SSHClient.exec_command,但捕获创建的通道并使用>>> import colorama # pip install colorama >>> colorama.ansi.clear_line() '\x1b[2K' >>> colorama.ansi.Cursor.UP() '\x1b[1A' ,如果命令运行时间过长,它将关闭该通道。

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