bash 使用 KeyboardInterrupt 异常捕获 SIGINT 在终端中有效,而不是在脚本中
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/40775054/
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
Capturing SIGINT using KeyboardInterrupt exception works in terminal, not in script
提问by Velda
I'm trying to catch SIGINT
(or keyboard interrupt) in Python 2.7 program. This is how my Python test script test
looks:
我正在尝试SIGINT
在 Python 2.7 程序中捕获(或键盘中断)。这是我的 Python 测试脚本的test
外观:
#!/usr/bin/python
import time
try:
time.sleep(100)
except KeyboardInterrupt:
pass
except:
print "error"
Next I have a shell script test.sh
:
接下来我有一个 shell 脚本test.sh
:
./test & pid=$!
sleep 1
kill -s 2 $pid
When I run the script with bash, or sh, or something bash test.sh
, the Python process test
stays running and is not killable with SIGINT
. Whereas when I copy test.sh
command and paste it into (bash) terminal, the Python process test
shuts down.
当我使用 bash 或 sh 或其他东西运行脚本时,bash test.sh
Python 进程test
保持运行并且不能被SIGINT
. 而当我复制test.sh
命令并将其粘贴到 (bash) 终端时,Python 进程test
将关闭。
I cannot get what's going on, which I'd like to understand. So, where is difference, and why?
我无法理解发生了什么,我想了解一下。那么,差异在哪里,为什么?
This is not about how to catch SIGINT
in Python!According to docs– this is the way, which should work:
这不是关于如何SIGINT
在 Python 中捕捉!根据文档- 这是应该工作的方式:
Python installs a small number of signal handlers by default: SIGPIPE ... and SIGINT is translated into a KeyboardInterrupt exception
Python 默认安装少量信号处理程序: SIGPIPE ... 和 SIGINT 被翻译成 KeyboardInterrupt 异常
It is indeed catching KeyboardInterrupt
when SIGINT
is sent by kill
if the program is started directly from shell, but when the program is started from bash script run on background, it seems that KeyboardInterrupt
is never raised.
如果程序直接从shell启动,它确实会捕获KeyboardInterrupt
whenSIGINT
发送kill
,但是当程序从后台运行的bash脚本启动时,似乎KeyboardInterrupt
从未引发过。
回答by mata
There is one case in which the default sigint handler is notinstalled at startup, and that is when the signal mask contains SIG_IGN
for SIGINT
at program startup. The code responsible for this can be found here.
有一种情况是在启动时没有安装默认的 sigint 处理程序,那就是在程序启动时信号掩码包含SIG_IGN
for SIGINT
。可以在此处找到对此负责的代码。
The signal mask for ignored signals is inherited from the parent process, while handled signals are reset to SIG_DFL
. So in case SIGINT
was ignored the condition if (Handlers[SIGINT].func == DefaultHandler)
in the source won't trigger and the default handler is not installed, python doesn't override the settings made by the parent process in this case.
忽略信号的信号掩码继承自父进程,而处理的信号则重置为SIG_DFL
。因此,如果SIGINT
忽略if (Handlers[SIGINT].func == DefaultHandler)
源中的条件不会触发并且未安装默认处理程序,则在这种情况下,python 不会覆盖父进程所做的设置。
So let's try to show the used signal handler in different situations:
因此,让我们尝试显示在不同情况下使用的信号处理程序:
# invocation from interactive shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))"
<built-in function default_int_handler>
# background job in interactive shell
$ python -c "import signal; print(signal.getsignal(signal.SIGINT))" &
<built-in function default_int_handler>
# invocation in non interactive shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))"'
<built-in function default_int_handler>
# background job in non-interactive shell
$ sh -c 'python -c "import signal; print(signal.getsignal(signal.SIGINT))" &'
1
So in the last example, SIGINT
is set to 1 (SIG_IGN
). This is the same as when you start a background job in a shell script, as those are non interactive by default (unless you use the -i
option in the shebang).
所以在最后一个例子中,SIGINT
被设置为 1 ( SIG_IGN
)。这与在 shell 脚本中启动后台作业时相同,因为默认情况下它们是非交互式的(除非您-i
在 shebang 中使用该选项)。
So this is caused by the shell ignoring the signal when launching a background job in a non interactive shell session, not by python directly. At least bash
and dash
behave this way, I've not tried other shells.
所以这是由 shell 在非交互式 shell 会话中启动后台作业时忽略信号引起的,而不是直接由 python 引起的。至少bash
并且dash
以这种方式行事,我还没有尝试过其他外壳。
There are two options to deal with this situation:
有两种选择来处理这种情况:
manually install the default signal handler:
import signal signal.signal(signal.SIGINT, signal.default_int_handler)
add the
-i
option to the shebang of the shell script, e.g:#!/bin/sh -i
手动安装默认信号处理程序:
import signal signal.signal(signal.SIGINT, signal.default_int_handler)
将
-i
选项添加到 shell 脚本的shebang,例如:#!/bin/sh -i
edit: this behaviour is documented in the bash manual:
编辑:此行为记录在 bash 手册中:
SIGNALS
...
When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.
SIGNALS
...
当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略 SIGINT 和 SIGQUIT。
which applies to non-interactive shells as they have job control disabled by default, and is actually specified in POSIX: Shell Command Language
这适用于非交互式 shell,因为它们默认禁用作业控制,实际上在 POSIX 中指定:Shell Command Language