我想使用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()
我希望命令在必要时运行,但我有两个问题:
shutdown()
命令 - 但这看起来很混乱。是否可以执行类似已发送Ctrl-C
到频道的stdin?readline()
阻止,如果我有一个非阻塞的输出方法,我可以避免线程 - 任何想法?答案 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