python SIGINT不终止调用外壳

python SIGINT不终止调用外壳

问题描述:

当从Linux shell(在bash和ksh中观察到同样的行为)运行Python并通过Ctl-C按键生成SIGINT时,我发现了我无法理解的行为,并且这让我非常沮丧。python SIGINT不终止调用外壳

当我按下Ctl-C时,Python进程正常终止,但shell继续执行下一个命令。

$ python -c "import time; time.sleep(100)"; echo END 
^CTraceback (most recent call last): 
    File "<string>", line 1, in <module> 
KeyboardInterrupt 
END 

相比之下,我的预期,并希望,该外壳处理信号以这样的方式,执行不继续下一个命令就行了,因为我看到的时候我所说的睡眠功能从bash子shell而不是从Python。

$ bash -c "sleep 100"; echo END 
^C 

Python 2和3被安装在我的系统,并同时产生上述捕获运行Python 2中,两个行为相同的方式。

$ python --version 
Python 2.7.14 
$ python3 --version 
Python 3.6.3 

我最好的解释是,当我按CTL-C,而Python的进程正在运行时,信号以某种方式直接进入Python的过程,而通常它是由主叫壳处理,然后传播到子。但是,我不知道Python为什么或者如何导致这种差异。

上面的例子是简单的测试,但在现实世界的使用中也观察到了这种行为。安装自定义信号处理程序不能解决问题。

经过相当多的挖掘后,我发现堆栈溢出几个松散相关的问题,最终导致我到article describing the proper handling of SIGINT。 (最相关的部分是如何成为一个合适的程序。)

从这些信息,我能够解决这个问题。没有它,我就永远不会接近。

的解决方案是最好用不能由一个键盘中断终止bash脚本开始说明,但是这确实隐藏Python的KeyboardInterrupt异常丑陋的堆栈跟踪:

#!/usr/bin/env bash                
echo "Press Ctrl-C to stop... No sorry it won't work." 
while true 
do 
     python -c ' 
import time, signal 
signal.signal(signal.SIGINT, signal.SIG_IGN) 
time.sleep(100) 
' 
done 

的变化,使外脚本处理中断是:

echo "Press Ctrl-C to stop..." 
while true 
do 
     python -c ' 
import time, signal, os 
signal.signal(signal.SIGINT, signal.SIG_DFL) 
time.sleep(100) 
' 
done 

但是,此解决方案使得不可能使用自定义处理程序(例如,执行清理)。如果要求这样做,则需要更复杂的方法:

#!/usr/bin/env bash 
echo "Press [CTRL+C] to stop ..." 
while true 
do 
     python -c ' 
import time, sys, signal, os 
def handle_int(signum, frame): 
    # Cleanup code here 
    signal.signal(signum, signal.SIG_DFL) 
    os.kill(os.getpid(), signum) 
signal.signal(signal.SIGINT, handle_int) 
time.sleep(100) 
' 
done 

的原因似乎是,除非内部过程终止通过执行由系统提供的默认SIGINT处理程序,父bash进程没有意识到该孩子因键盘中断而终止,并且本身不终止。

我还没有完全理解所有的辅助问题,比如父进程是不是从系统接收到SIGINT,还是正在接收信号,但忽略它。我也不知道默认处理程序是做什么的,或者父母如何检测它被调用。如果我能够学到更多,我会提供更新。

我必须提出一个问题:Python的当前行为是否应该被认为是Python中的一个设计缺陷。多年来,在从shell脚本中调用Python的过程中,我已经看到了这个问题的各种表现,但直到现在还没有调查过。但是,我还没有通过网络搜索找到一篇文章。如果问题确实代表了一个缺陷,那么让我感到惊讶的是,并不是很多开发人员受到影响。

任何获取CTRL + C的程序的行为都取决于该程序。通常行为是退出,但一些程序可能会放弃一些内部程序而不是停止整个程序。这甚至有可能(尽管可能被认为是不礼貌的做法)让程序完全忽略击键。

程序的行为是由它设置的信号处理程序定义的。 C库提供了默认的信号处理程序(它们在SIGTERM和SIGINT中执行退出操作),但是程序可以提供自己的处理程序来运行。并非所有信号都允许任意响应。例如,SIGSEGV(seg-fault)需要程序退出,尽管它可以配置其信号处理程序来进行核心转储。 SIGKILL根本无法处理(操作系统内核负责它)。

要在Python中定制信号处理程序,您需要使用标准库中的the signal module。您可以拨打signal.signal为系统C库定义的任何信号设置您自己的信号处理函数。键入CTRL + C将在任何基于UNIX的系统上发送SIGINT,所以这可能是您想要处理的内容,如果您想要自己的行为。

尝试是这样的:

import signal 
import sys 
import time 

def interrupt_handler(sig, frame): 
    sys.exit(1) 

signal.signal(signal.SIGINT, interrupt_handler) 

time.sleep(100) 

如果您运行此脚本,用CTRL + C中断它,它应该默默退出,就像你的bash脚本一样。

+0

学到了新的东西,谢谢你的回答。 – user1767754

+0

我的问题涉及bash是否继续处理下一个命令,而不是Python是否打印堆栈跟踪。这种差异通过是否将“END”输出到控制台来显示。如果你重读这个问题,你会看到我指出自定义信号处理程序不能解决这个特定问题,尽管如你所说,它们将是避免堆栈跟踪的方式。 – epl

+0

啊,我明白了。为此,您应该在命令行中使用'&&',而不是';'。 '&&'会使第二个命令只在第一个命令以代码'0'退出(通常意味着成功)时才运行。如果Python中断,Python会设置代码为'1'(默认情况下,你不需要自定义信号处理程序,尽管我也是这样做的)。我不知道为什么shell有时在';'之后运行命令,有时不会。你可能想用'bash'(或者你的shell是什么)来标记问题来得到关于这个行为的答案。 – Blckknght