关于python:使用KeyboardInterrupt异常捕获SIGINT在终端中工作,而不是在脚本中

Capturing SIGINT using KeyboardInterrupt exception works in terminal, not in script

我试图在Python 2.7程序中捕获SIGINT(或键盘中断)。 这就是我的Python测试脚本test的外观:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python

import time

try:
    time.sleep(100)
except KeyboardInterrupt:
    pass
except:
    print"error"

接下来我有一个shell脚本test.sh

1
2
3
./test & pid=$!
sleep 1
kill -s 2 $pid

当我使用bash,或sh或bash test.sh运行脚本时,Python进程test保持运行并且不能用SIGINT运行。 然而,当我复制test.sh命令并将其粘贴到(bash)终端时,Python进程test将关闭。

我不知道发生了什么,我想了解。 那么,差异在哪里,为什么?

这不是关于如何在Python中捕获SIGINT! 根据文档 - 这是应该工作的方式:

Python installs a small number of signal handlers by default: SIGPIPE ... and SIGINT is translated into a KeyboardInterrupt exception

如果程序是直接从shell启动的话,当SIGINTkill发送时确实正在捕捉KeyboardInterrupt,但是当程序从bash脚本在后台运行时启动时,似乎永远不会引发KeyboardInterrupt


有一种情况是在启动时未安装默认的sigint处理程序,即在程序启动时信号掩码包含SIG_IGN SIGINT的情况。可以在此处找到负责此操作的代码。

忽略信号的信号掩码从父进程继承,而处理信号则重置为SIG_DFL。因此,如果忽略SIGINT,源中的条件if (Handlers[SIGINT].func == DefaultHandler)将不会触发,并且未安装默认处理程序,在这种情况下,python不会覆盖父进程所做的设置。

因此,让我们尝试在不同的情况下显示使用过的信号处理程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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

所以在最后一个例子中,SIGINT设置为1(SIG_IGN)。这与在shell脚本中启动后台作业时相同,因为默认情况下这些作业是非交互式的(除非您在shebang中使用-i选项)。

所以这是由shell在非交互式shell会话中启动后台作业时忽略信号引起的,而不是直接由python引起的。至少bashdash表现得这样,我没有尝试过其他的shell。

有两种方法可以解决这种情况:

  • 手动安装默认信号处理程序:

    1
    2
    import signal
    signal.signal(signal.SIGINT, signal.default_int_handler)
  • -i选项添加到shell脚本的shebang中,例如:

    1
    #!/bin/sh -i

编辑:bash手册中记录了此行为:

SIGNALS
...
When job control is not in effect, asynchronous commands ignore SIGINT and SIGQUIT in addition to these inherited handlers.

它适用于非交互式shell,因为它们默认禁用了作业控制,实际上是在POSIX:Shell Command Language中指定的