25

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

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到频道的标准输入的操作?
  2. readline()块,如果我有一种获取输出的非阻塞方法,我可以避免使用线程——有什么想法吗?
4

6 回答 6

23

不要在客户端调用 exec_command,而是获取传输并生成您自己的通道。通道可用于执行命令,您可以在 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)

可以读取和写入通道对象,连接远程命令的标准输出和标准输入。您可以通过调用 stderr 来获取channel.makefile_stderr(...)

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

于 2009-04-19T22:27:05.900 回答
15

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

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

于 2009-05-07T17:43:22.737 回答
9

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)
于 2013-02-15T04:40:38.033 回答
0

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

interface.close()

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

于 2009-04-18T00:58:10.717 回答
0

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

于 2012-06-25T14:02:15.117 回答
0

我解决这个问题的方法是使用上下文管理器。这将确保我长时间运行的命令被中止。关键逻辑是包装以模仿 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
于 2020-03-26T03:20:26.317 回答