使用KeyboardInterrupt异常捕获SIGINT在终端中起作用,而不在脚本中起作用 [英] Capturing SIGINT using KeyboardInterrupt exception works in terminal, not in script

查看:203
本文介绍了使用KeyboardInterrupt异常捕获SIGINT在终端中起作用,而不在脚本中起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to catch SIGINT (or keyboard interrupt) in Python 2.7 program. This is how my Python test script test looks:

#!/usr/bin/python

import time

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

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

Next I have a shell script test.sh:

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

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

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.

我无法理解所发生的事情.那么,区别在哪里,为什么呢?

I cannot get what's going on, which I'd like to understand. So, where is difference, and why?

这与如何在Python中捕获SIGINT无关!根据文档 –这是应该起作用的方式:

This is not about how to catch SIGINT in Python! According to docs – this is the way, which should work:

Python默认情况下会安装少量信号处理程序:SIGPIPE ...并将SIGINT转换为KeyboardInterrupt异常

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

如果直接从shell启动程序,则KeyboardInterrupt确实会捕获KeyboardInterrupt,但是从后台运行的bash脚本启动程序时,似乎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.

推荐答案

在一种情况下,默认的sigint处理程序在启动时未安装 ,即信号掩码包含在程序启动时用于SIGINT.可以在此处找到该代码.

There is one case in which the default sigint handler is not installed 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.

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

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.

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

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

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

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).

因此,这是由外壳程序在非交互式外壳程序会话中启动后台作业时忽略信号引起的,而不是由python直接引起的.至少bashdash这样,我没有尝试过其他shell.

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.

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

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)

  • -i选项添加到shell脚本的shebang中,例如:

  • add the -i option to the shebang of the shell script, e.g:

    #!/bin/sh -i
    

  • 此行为记录在bash手册中:

    edit: this behaviour is documented in the bash manual:

    信号
    ...
    当作业控制无效时,除了这些继承的处理程序之外,异步命令还会忽略SIGINT和SIGQUIT.

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

    适用于非交互式shell,因为它们默认情况下已禁用作业控制,并且实际上是在POSIX中指定的:

    which applies to non-interactive shells as they have job control disabled by default, and is actually specified in POSIX: Shell Command Language

    这篇关于使用KeyboardInterrupt异常捕获SIGINT在终端中起作用,而不在脚本中起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆