【问题标题】:Send Ctrl-C to remote processes started via subprocess.Popen and ssh将 Ctrl-C 发送到通过 subprocess.Popen 和 ssh 启动的远程进程
【发布时间】:2018-05-08 04:41:10
【问题描述】:

如何将Ctrl-C 发送到Popen() 对象中的多个ssh -t 进程?

我有一些 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 并不能可靠地杀死所有东西,而且我的终端之后总是会出现乱码(我必须运行“重置”)。那么当主脚本被杀死时,我怎样才能杀死两个远程脚本呢?

注意:我试图避免登录远程主机,在进程列表中搜索“script.sh”,并向两个进程发送 SIGINT。我只想能够在启动脚本上按Ctrl-C,并让它杀死两个远程进程。一个不太理想的解决方案可能涉及确定性地查找远程脚本的 PID,但我不知道如何在我当前的设置中做到这一点。

更新:在远程服务器上启动的脚本实际上启动了几个子进程,并且在杀死 ssh 时确实杀死了原始远程脚本(可能是 SIGHUP 的 b/c),子任务没有被杀死.

【问题讨论】:

  • 我将标题更改为实际描述您想要做的事情。
  • 不知道它是否会起作用,但是您是否尝试过将文本字节“\x03”的结尾发送到子进程?这相当于 Ctrl-C。
  • @Thomas K:好主意,但不幸的是,只有将“\x03”发送到进程所连接的终端的输入端(或者当然,如果程序解释数据那样!)......遗憾的是,在这种情况下,子进程是通过管道而不是终端,所以将 Ctrl-C 转换为 SIGINT 的处理不存在:(
  • 什么远程程序让终端乱码?
  • 当我说“我的终端出现乱码”时,我的意思是它停止输出 '\r' 字符并且它不会回显我输入的内容。一旦我运行“重置”,它就会恢复正常。

标签: python unix ssh popen


【解决方案1】:

我能够成功杀死所有子进程的唯一方法是使用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()

这已经足够可靠了。我相信其他人之前发布了这个答案,但后来删除了答案,所以我会发布它以防其他人好奇。

【讨论】:

  • 太棒了!非常感谢您发布这个。多年来一直试图解决这个问题。
【解决方案2】:

当被杀死时,ssh 会向远程进程发送一个 SIGHUP。您可以将远程进程包装到 shell 或 python 脚本中,当该脚本接收到 SIGHUP 时将杀死它们(请参阅 bash 的陷阱命令和 python 中的信号模块)

甚至可以使用臃肿的命令行而不是远程包装脚本来完成。

问题是杀死远程进程不是你想要的,你想要的是在你执行 Ctrl+C 之后有一个工作终端。为此,您必须终止远程进程并查看剩余的输出,其中将包含一些终端控制序列以将终端重置为正确的状态。为此,您将需要一种机制来指示包装脚本以终止进程。这不是一回事。

【讨论】:

  • SIGHUP 的默认操作是终止,因此您甚至可能不需要包装器。
  • @psmears:如果是这样的话,提问者不会有任何问题,问题也不存在。
  • 我假设那是因为由于某种原因信号没有在本地正确传递(即 ssh 以某种方式在后台运行)。
  • 这是正确的,但它似乎对我不起作用,我认为因为我启动的脚本继续启动其他几个脚本,所以只有启动的原始脚本死亡。
【解决方案3】:

我还没有尝试过,但也许你可以捕获一个 KeyboardInterrupt 然后终止进程:

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)

【讨论】:

  • Popen 对象实际上有一个.kill() 方法。但正如奇点所说,挑战在于杀死远程进程,而不是本地进程。我认为没有任何简单的方法可以做到这一点,因为 Python 只知道本地进程。
  • @singularity:你说得对,这会杀死本地进程,但结果通常也会杀死远程进程(参见 BatchyX 的回答)。
【解决方案4】:

我通过取消映射我关心的所有信号来解决类似的问题。当按下 Ctrl+C 时,它仍然会传递给子进程,但 Python 会等到子进程退出后再处理主脚本中的信号。只要子进程响应 Ctrl+C,这对于信号子进程就可以正常工作。

class DelayedSignalHandler(object):
    def __init__(self, managed_signals):
        self.managed_signals = managed_signals
        self.managed_signals_queue = list()
        self.old_handlers = dict()

    def _handle_signal(self, caught_signal, frame):
        self.managed_signals_queue.append((caught_signal, frame))

    def __enter__(self):
        for managed_signal in self.managed_signals:
            old_handler = signal.signal(managed_signal, self._handle_signal)
            self.old_handlers[managed_signal] = old_handler

    def __exit__(self, *_):
        for managed_signal, old_handler in self.old_handlers.iteritems():
            signal.signal(managed_signal, old_handler)

        for managed_signal, frame in self.managed_signals_queue:
            self.old_handlers[managed_signal](managed_signal, frame)

现在,我的子流程代码如下所示:

    with DelayedSignalHandler((signal.SIGINT, signal.SIGTERM, signal.SIGHUP)):
        exit_value = subprocess.call(command_and_arguments)

每当按下 Ctrl+C 时,允许应用程序在处理信号之前退出,因此您不必担心终端会因为子进程线程没有与主进程线程同时终止而出现乱码.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-03
    • 2017-10-02
    • 2014-08-25
    • 1970-01-01
    • 1970-01-01
    • 2010-09-21
    相关资源
    最近更新 更多