我试图在Python 2.7程序中捕获SIGINT
(或键盘中断)。这就是我的Python测试脚本test
的外观:
#!/usr/bin/python
import time
try:
time.sleep(100)
except KeyboardInterrupt:
pass
except:
print "error"
接下来我有一个shell脚本test.sh
:
./test & pid=$!
sleep 1
kill -s 2 $pid
当我使用bash或sh或bash test.sh
运行脚本时,Python进程test
仍在运行,并且无法使用SIGINT
。而当我复制test.sh
命令并将其粘贴到(bash)终端时,Python进程test
将关闭。
我无法理解正在发生的事情,我想了解一下。那么,差异在哪里,为什么?
这不是关于如何在Python中捕获SIGINT
!根据docs - 这是应该有效的方式:
Python默认安装少量信号处理程序:SIGPIPE ...并且SIGINT被转换为KeyboardInterrupt异常
如果程序是直接从shell启动的,KeyboardInterrupt
发送SIGINT
时确实正在捕获kill
,但是当程序是从bash脚本启动时运行的背景,似乎<{1}}永远不会被提升。
答案 0 :(得分:19)
有一种情况是默认的sigint处理程序在启动时不安装,也就是在程序启动时信号掩码包含SIG_IGN
SIGINT
的情况。可以找到负责此操作的代码here。
忽略信号的信号掩码从父进程继承,处理信号重置为SIG_DFL
。因此,如果SIGINT
被忽略,则源中的条件if (Handlers[SIGINT].func == DefaultHandler)
不会触发并且未安装默认处理程序,python不会覆盖父进程在此中所做的设置情况下。
因此,让我们尝试在不同情况下显示使用过的信号处理程序:
# 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引起的。至少bash
和dash
表现得这样,我没有尝试其他贝壳。
有两种方法可以解决这种情况:
手动安装默认信号处理程序:
import signal
signal.signal(signal.SIGINT, signal.default_int_handler)
将-i
选项添加到shell脚本的shebang中,例如:
#!/bin/sh -i
编辑:bash手册中记录了这种行为:
SIGNALS
...
当作业控制不起作用时,除了这些继承的处理程序之外,异步命令还会忽略SIGINT和SIGQUIT。
适用于非交互式shell,因为默认情况下它们已禁用作业控制,并且实际上在POSIX中指定:Shell Command Language