如何从 bash 脚本向 Python 发送 SIGINT?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/974189/
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-09-17 20:56:44  来源:igfitidea点击:

How to send a SIGINT to Python from a bash script?

pythonbash

提问by Ryan

I want to launch a background Python job from a bash script and then gracefully kill it with SIGINT. This works fine from the shell, but I can't seem to get it to work in a script.

我想从 bash 脚本启动后台 Python 作业,然后使用 SIGINT 优雅地终止它。这在 shell 中工作正常,但我似乎无法让它在脚本中工作。

loop.py:

循环.py:

#! /usr/bin/env python
if __name__ == "__main__":
    try:
        print 'starting loop'
        while True:
            pass
    except KeyboardInterrupt:
        print 'quitting loop'

From the shell I can interrupt it:

从 shell 我可以中断它:

$ python loop.py &
[1] 15420
starting loop
$ kill -SIGINT 15420
quitting loop
[1]+  Done                    python loop.py

kill.sh:

杀.sh:

#! /bin/bash
python loop.py &
PID=$!
echo "sending SIGINT to process $PID"
kill -SIGINT $PID

But from a script I can't:

但是从脚本中我不能:

$ ./kill.sh 
starting loop
sending SIGINT to process 15452
$ ps ax | grep loop.py | grep -v grep
15452 pts/3    R      0:08 python loop.py

And, if it's been launched from a script I can no longer kill it from the shell:

而且,如果它是从脚本启动的,我将无法再从 shell 中杀死它:

$ kill -SIGINT 15452
$ ps ax | grep loop.py | grep -v grep
15452 pts/3    R      0:34 python loop.py

I'm assuming I'm missing some fine point of bash job control.

我假设我错过了 bash 作业控制的一些细节。

回答by Matthew Flaschen

You're not registering a signal handler. Try the below. It seems to work fairly reliably. I think the rare exception is when it catches the signal before Python registers the script's handler. Note that KeyboardInterrupt is only supposed to be raised, "when the user hits the interrupt key". I think the fact that it works for a explicit (e.g. via kill) SIGINT at all is an accident of implementation.

您没有注册信号处理程序。试试下面的。它似乎相当可靠地工作。我认为罕见的例外是在 Python 注册脚本处理程序之前捕获信号。请注意,KeyboardInterrupt 只应该在“用户按下中断键时”引发。我认为它完全适用于显式(例如通过终止)SIGINT 的事实是实现的意外。

import signal

def quit_gracefully(*args):
    print 'quitting loop'
    exit(0);

if __name__ == "__main__":
    signal.signal(signal.SIGINT, quit_gracefully)

    try:
        print 'starting loop'
        while True:
            pass
    except KeyboardInterrupt:
        quit_gracefully()

回答by Michiel Buddingh

I agree with Matthew Flaschen; the problem is with python, which apparently doesn't register the KeyboardInterrupt exception with SIGINT when it's not called from an interactive shell.

我同意 Matthew Flaschen 的观点;问题出在 python 上,当它不是从交互式 shell 调用时,它显然没有用 SIGINT 注册 KeyboardInterrupt 异常。

Of course, nothing prevents you from registering your signal handler like this:

当然,没有什么可以阻止您像这样注册信号处理程序:

def signal_handler(signum, frame):
    raise KeyboardInterrupt, "Signal handler"

回答by Steen

In addition to @matthew-flaschen's answer, you can use execin the bash script to effectively replace the scope to the process being opened:

除了@matthew-flaschen 的回答之外,您还可以exec在 bash 脚本中使用以有效地将范围替换为正在打开的进程:

#!/bin/bash
exec python loop.py &
PID=$!
sleep 5  # waiting for the python process to come up

echo "sending SIGINT to process $PID"
kill -SIGINT $PID

回答by tomoe

When you run command in background with &, SIGINT will be ignored. Here's the relevant section of man bash:

当您使用 & 在后台运行命令时,SIGINT 将被忽略。这是 man bash 的相关部分:

Non-builtin commands run by bash have signal handlers set to the values inherited by the shell from its parent. When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers. Commands run as a result of command substitution ignore the keyboard-generated job control signals SIGTTIN, SIGTTOU, and SIGTSTP.

bash 运行的非内置命令将信号处理程序设置为 shell 从其父级继承的值。当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略 SIGINT 和 SIGQUIT。由于命令替换而运行的命令会忽略键盘生成的作业控制信号 SIGTTIN、SIGTTOU 和 SIGTSTP。

I think you need to set signal handler explicitly as Matthew commented.

我认为您需要像 Matthew 评论的那样明确设置信号处理程序。

The script kill.sh also have a problem. Since loop.py is sent to background, there's no guarantee that kill runs after python loop.py.

脚本kill.sh也有问题。因为 loop.py 被发送到后台,所以不能保证在 python loop.py 之后运行 kill。

#! /bin/bash
python loop.py &
PID=$!
#
# NEED TO WAIT ON EXISTENCE OF python loop.py PROCESS HERE.
#
echo "sending SIGINT to process $PID"
kill -SIGINT $PID

回答by Eric Cousineau

Tried @Steen's approach, but alas, it does not apparently hold on Mac.

尝试过@Steen 的方法,但可惜的是,它显然不适用于 Mac。

Another solution, pretty much the same as the above but a little more general, is to just re-install the default handler if SIGINTis being ignored:

另一个解决方案,与上面的几乎相同,但更通用,如果SIGINT被忽略,则重新安装默认处理程序:

def _ensure_sigint_handler():
    # On Mac, even using `exec <cmd>` in `bash` still yields an ignored SIGINT.
    sig = signal.getsignal(signal.SIGINT)
    if signal.getsignal(signal.SIGINT) == signal.SIG_IGN:
        signal.signal(signal.SIGINT, signal.default_int_handler)
# ...
_ensure_sigint_handler()