通过subprocess.Popen和ssh向远程进程发送Ctrl-C

21 投票
4 回答
30914 浏览
提问于 2025-04-16 09:50

我该如何给多个 ssh -t 进程发送 Ctrl-C 呢?

我有一些Python代码,可以在远程主机上启动一个脚本:

# kickoff.py

# i call 'ssh' w/ the '-t' flag so that when i press 'ctrl-c', it get's
# sent to the script on the remote host.  otherwise 'ctrol-c' would just
# kill things on this end, and the script would still be running on the
# remote server
a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
a.communicate()

这个方法很好用,但我需要在远程主机上同时启动多个脚本:

# kickoff.py

a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b'])
a.communicate()
b.communicate()

结果是,按 Ctrl-C 并不能可靠地终止所有进程,而且我的终端在之后总是会出现乱码(我得运行 'reset' 来恢复)。那么,如何在主脚本被终止时同时杀掉两个远程脚本呢?

注意:我想避免登录到远程主机,查找进程列表中的 'script.sh',然后给两个进程发送 SIGINT 信号。我只想在启动脚本时按 Ctrl-C,就能同时终止两个远程进程。一个不太理想的解决方案可能是确定性地找到远程脚本的进程ID,但我不知道在我现在的设置中该怎么做。

更新:在远程服务器上启动的脚本实际上会启动几个子进程,虽然杀掉 ssh 确实能终止原始的远程脚本(可能是因为 SIGHUP 信号),但子任务并不会被终止。

4 个回答

3

我还没试过这个,不过你可以试着捕捉一个键盘中断,然后结束那些进程:

try
    a = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'a'])
    b = subprocess.Popen(['ssh', '-t', 'remote-host', './script.sh', 'b'])
    a.communicate()
    b.communicate()
except KeyboardInterrupt:
    os.kill(a.pid, signal.SIGTERM)
    os.kill(b.pid, signal.SIGTERM)
4

当你结束ssh连接时,它会向远程的进程发送一个叫做SIGHUP的信号。你可以把这些远程进程放在一个shell脚本或者python脚本里,这样当这个脚本收到SIGHUP信号时,就会把它们杀掉(在bash中可以用trap命令,python里有signal模块)。

其实,你也可以用一个复杂的命令行来实现,而不一定非要用远程的包装脚本。

问题在于,杀掉远程进程并不是你真正想要的。你真正想要的是在按下Ctrl+C后,终端还能正常工作。为了做到这一点,你需要同时杀掉远程进程,并且看到剩下的输出,这些输出里会有一些终端控制序列,用来把终端恢复到正常状态。为此,你需要一种机制来通知包装脚本去杀掉这些进程。这两者是不同的事情。

11

我唯一能成功结束所有子进程的方法是使用pexpect这个工具:

a = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'a'])
a.expect('something')

b = pexpect.spawn(['ssh', 'remote-host', './script.sh', 'b'])
b.expect('something else')

# ...

# to kill ALL of the children
a.sendcontrol('c')
a.close()

b.sendcontrol('c')
b.close()

这个方法挺可靠的。我记得之前有人也发过这个答案,但后来把它删掉了,所以我就把它发出来,以防其他人也想知道。

撰写回答