Python 将 Ctrl-C 发送到通过 subprocess.Popen 和 ssh 启动的远程进程

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4669204/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 16:50:50  来源:igfitidea点击:

Send Ctrl-C to remote processes started via subprocess.Popen and ssh

pythonunixsshpopen

提问by aaronstacy

How do I send a Ctrl-Cto multiple ssh -tprocesses in Popen()objects?

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

I have some Python code that kicks off a script on a remote host:

我有一些 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()

That works great, but I need to kick off multiple scripts on the remote host:

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

# 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()

The result of this is that Ctrl-Cdoesn't reliably kill everything, and my terminal always gets garbled afterwards (I have to run 'reset'). So how can I kill both remote scripts when the main one is killed?

这样做的结果是Ctrl-C并不能可靠地杀死所有内容,而且我的终端之后总是出现乱码(我必须运行“重置”)。那么当主要脚本被杀死时,我如何杀死两个远程脚本?

Note: I'm trying to avoid logging into the remote-host, searching for 'script.sh' in the process list, and sending a SIGINT to both of the processes. I just want to be able to press Ctrl-Con the kickoff script, and have that kill both remote processes. A less optimal solution may involve deterministically finding the PID's of the remote scripts, but I don't know how to do that in my current set-up.

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

Update: the script that gets kicked off on the remote server actually starts up several children processes, and while killing the sshdoes kill the original remote script (probably b/c of SIGHUP), the children tasks are not killed.

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

采纳答案by aaronstacy

The only way I was able to successfully kill all of my child processes was by using pexpect:

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

This is reliable enough. I believe someone else posted this answer earlier, but then deleted the answer, so I will post it in case someone else is curious.

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

回答by lauret

I haven't tried this, but maybe you can catch a KeyboardInterrupt and then kill the processes:

我没有试过这个,但也许你可以抓住一个 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)

回答by BatchyX

When killed, ssh will send a SIGHUP to the remote processes. You could wrap the remote processes into a shell or python script that will kill them when that script receives a SIGHUP (see the trap command for bash, and the signal module in python)

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

It might even be possible to do it with a bloated command line instead of a remote wrapper script.

甚至可以使用臃肿的命令行而不是远程包装器脚本来做到这一点。

The problem is that killing the remote processes is not what you want, what you want is to have a working terminal after you do Ctrl+C. to do that, you will have to kill the remote processes AND see the remaining output, which will contain some terminal control sequences to reset the terminal to a proper state. For that you will need a mecanism to signal a wrapper script to kill the processes. This is not the same thing.

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

回答by Eric Pruitt

I worked around a similar problem this problem by unmapping all of the signals I cared about. When Ctrl+C is pressed, it will still be passed through to the subprocess but Python will wait until the subprocess exits before handling the signal in the main script. This works fine for a signal subprocess as long as the subprocess responds to Ctrl+C.

我通过取消映射我关心的所有信号来解决类似的问题。当按下 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)

Now, my subprocess code looks like this:

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

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

Whenever Ctrl+C is pressed, the application is allowed to exit before the signal is handled so you don't have to worry about the terminal getting garbled because the subprocess thread was not terminated at the same time as the main process thread.

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