Paramiko和exec_command - 如何终止远程进程?

16 投票
8 回答
30415 浏览
提问于 2025-04-17 04:08

我正在使用Paramiko来对远程服务器上的一个文件进行tail -f操作。

之前我们是通过ssh -t来运行这个操作的,但这样做不太稳定,而且-t选项还导致了我们远程调度系统的一些问题。

我想问的是,当脚本捕捉到SIGINT信号时,怎么能结束tail命令?

我的脚本是基于Python Paramiko模块中的长时间运行的ssh命令(以及如何结束它们)

#!/usr/bin/env python2
import paramiko
import select

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('someserver', username='victorhooi', password='blahblah')
transport = client.get_transport()
channel = transport.open_session()

channel.exec_command("tail -f /home/victorhooi/macbeth.txt")
while True:
    try:
        rl, wl, xl = select.select([channel],[],[],0.0)
        if len(rl) > 0:
            # Must be stdout
            print channel.recv(1024)
    except KeyboardInterrupt:
        print("Caught control-C")
        client.close()
        channel.close()
        exit(0)

这个脚本成功捕捉到了我的Ctrl-C操作,并且结束了。但是,它却让远程系统上的tail -f进程继续运行。

无论是client.close()还是channel.close()都似乎无法终止它。

在异常处理块中,我可以发出什么命令来结束它呢?

远程服务器运行的是Solaris 10。

8 个回答

7

有一种方法可以做到这一点。它的工作方式就像在命令行中一样。

ssh -t commandname

选项 -t 是用来打开一个伪终端,这样 ssh 就能跟踪这个过程应该持续多久。通过 pormiko 也可以做到这一点,方法是

channel.get_pty()

在执行 execute_command(...) 之前。这不会像 channel.invoke_shell() 那样打开一个真正的命令行,它只是请求一个伪接口来把所有的进程连接起来。如果在远程机器上运行 ps aux 命令,你会看到这个进程现在是和 sshd 通过一个 ptxXY 接口连接在一起的。

8

你应该使用ssh保持连接的功能……你遇到的问题是,远程的命令行默认情况下不知道你的ssh连接已经断开。保持连接的功能可以让远程的命令行检测到你已经结束了这个连接。

client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('someserver', username='victorhooi', password='blahblah')
transport = client.get_transport()
transport.set_keepalive(1)   # <------------------------------
# ... carry on as usual...

你可以把保持连接的时间设置得很短(甚至1秒钟)……过了几秒钟,远程的命令行就会发现ssh登录已经断开,然后它会结束由这个连接启动的任何进程。

1

虽然这个方法不是最有效的,但应该能奏效。在你按下 CTRL+C 后,在键盘中断处理程序里,你可以像这样执行 exec_command("killall -u %s tail" % uname)

#!/usr/bin/env python2

import paramiko
import select

import time
ltime = time.time()

# Or use random:
# import random
# ltime = random.randint(0, 500)

uname = "victorhooi"
client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('someserver', username=uname, password='blahblah')
transport = client.get_transport()
channel = transport.open_session()

channel.exec_command("tail -%df /home/victorhooi/macbeth.txt" % ltime)
while True:
    try:
        rl, wl, xl = select.select([channel],[],[],0.0)
        if len(rl) > 0:
            # Must be stdout
            print channel.recv(1024)
    except KeyboardInterrupt:
        print("Caught control-C")
        channel.close()
        try:
            # open new socket and kill the proc..
            client.get_transport().open_session().exec_command("kill -9 `ps -fu %s | grep 'tail -%df /home/victorhooi/macbeth.txt' | grep -v grep | awk '{print $2}'`" % (uname, ltime))
        except:
            pass
    
        client.close()
        exit(0)

这条命令会结束所有名为 tail 的进程。不过,如果你有一些 tail 进程是你不想关闭的,这样做可能会有问题。如果是这种情况,你可以用 grep 来查找 ps 的输出,获取进程ID(pid),然后用 kill -9 来结束它。

首先,设置 tail 从文件末尾读取 n 行,然后再跟踪后面的内容。将 n 设置为一个独特的数字,比如 time.time(),因为 tail 不在乎这个数字是否大于文件中的行数,使用 time.time() 得到的大数字不会造成问题,并且是独一无二的。接着在 ps 中用 grep 查找这个独特的数字:

   client.get_transport().open_session().exec_command("kill -9 `ps -fu %s | grep 'tail -%df /home/victorhooi/macbeth.txt' | grep -v grep | awk '{print $2}'`" % (uname, ltime))

撰写回答