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

14
我想从一个bash脚本中启动一个后台Python作业,然后使用SIGINT优雅地杀掉它。这在shell中很好用,但我似乎无法在脚本中使其工作。 loop.py:
#! /usr/bin/env python
if __name__ == "__main__":
    try:
        print 'starting loop'
        while True:
            pass
    except KeyboardInterrupt:
        print 'quitting loop'

从 shell 中我可以中断它:

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

kill.sh:

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

但是从脚本中我做不到:

$ ./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

如果它是从脚本启动的,我就无法从Shell中杀死它了:

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

我认为我缺少一些 bash 作业控制的细节。

5个回答

17

你没有注册一个信号处理器。尝试下面的方法。它似乎相当可靠。我认为罕见的异常情况是在Python注册脚本处理程序之前捕获信号。请注意,KeyboardInterrupt只应该在“用户按下中断键”时引发。我认为它可以通过显式的(例如通过kill)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()

18.8. 信号 - 为异步事件设置处理程序 它适用于同步命令的事实并非偶然。 - Wyrmwood
该解决方案无法在尝试从shell脚本向Python脚本发送-SIGINT信号时工作。(如@Ryan的问题中所述) - user3156262

7
除了@matthew-flaschen的答案之外,你可以在bash脚本中使用exec来有效地将作用域替换为被打开的进程:
#!/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

1
这似乎是最简单的方法,对我有用(在Ubuntu 16.04.3上使用bash 4.3.48)。谢谢! - Eric Cousineau
嘿,看起来在Mac上不是这样的。稍后会发布跟进。 - Eric Cousineau
好的!运行成功了,厉害啊!=D - Eduardo Lucio

4

我同意Matthew Flaschen的看法;问题出在Python上,当它不是从交互式shell中调用时,显然无法将KeyboardInterrupt异常与SIGINT注册。

当然,你可以像这样注册你的信号处理程序:

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

2
当你在后台运行带有 & 符号的命令时,SIGINT 会被忽略。以下是 bash 手册中相关部分的内容:
非内置命令由 bash 运行,其信号处理程序设置为从其父进程继承的值。当作业控制未生效时,异步命令会忽略 SIGINT 和 SIGQUIT,除了这些继承处理程序之外。作为命令替换结果运行的命令会忽略键盘生成的作业控制信号 SIGTTIN、SIGTTOU 和 SIGTSTP。
我认为你需要像 Matthew 评论中所说的那样显式设置信号处理程序。
脚本 kill.sh 也存在问题。由于 loop.py 被发送到后台,不能保证 kill 在 python loop.py 运行之后运行。
#! /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

1
尝试了@Steen的方法,但遗憾的是,在Mac上似乎不起作用。
另一个解决方案,与上面的解决方案几乎相同,但更加通用,就是在忽略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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接