linux Python SIGINT未终止调用shell

1aaf6o9v  于 2022-12-18  发布在  Linux
关注(0)|答案(3)|浏览(171)

当从Linux shell运行Python时(在bash和ksh中观察到相同的行为),并使用Ctl-C键生成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

相比之下,我曾经期望,并且希望shell处理信号的方式不会继续执行该行中的下一个命令,正如我从bash子shell而不是Python调用sleep函数时所看到的那样。
例如,我希望上面的捕获看起来更类似于下面的内容:

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

我的系统上安装了Python 2和Python 3,虽然上面的捕获是在运行Python 2时生成的,但两者的行为方式相同。
我最好的解释是,当我在Python进程运行时按下Ctl-C键时,信号不知何故直接进入Python进程,而通常情况下,它是由调用shell处理的,然后传播到子进程,但是,我不知道Python为什么或如何造成这种差异。
上面的例子是简单的测试,但是在实际使用中也会观察到这种行为。安装自定义信号处理程序并不能解决这个问题。

4xrmg8kj

4xrmg8kj1#

经过大量的挖掘,我发现了一些关于Stack Overflow的松散相关的问题,这些问题最终将我引向了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时,我看到了这个问题的各种表现形式,但直到现在才有机会进行调查。然而,我通过Web搜索没有找到一篇关于这个主题的文章。如果这个问题确实代表了一个缺陷,令我惊讶的是,受影响的开发商并不多。

hgb9j2n6

hgb9j2n62#

任何一个得到CTRL+C键的程序的行为都取决于那个程序。通常这个行为是退出,但是有些程序可能只是中止一些内部过程而不是停止整个程序。甚至有可能(尽管这可能被认为是不礼貌的)程序完全忽略这个键击。
程序的行为由它设置的信号处理程序定义。C库提供默认的信号处理程序(在SIGTERM和SIGINT上执行类似exit的操作),但是程序可以提供自己的处理程序来代替它运行。不是所有的信号都允许任意响应。例如SIGSEGV(一个seg-fault)要求程序退出,尽管它可以配置它的信号处理程序来进行核心转储。SIGKILL根本不能被处理(操作系统内核会处理它)。
要在Python中定制信号处理程序,你需要使用标准库中的the signal module,你可以调用signal.signal来为你系统的C库定义的任何信号设置你自己的信号处理函数,在任何基于UNIX的系统上键入CTRL+C都会发送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脚本一样。

carvr3hs

carvr3hs3#

您可以在bash端的脚本文件中显式地处理它,如下所示:

if python -c "import time; time.sleep(100)"; then
  echo END
fi

或者更激进地说

python -c "import time; time.sleep(100)"
[[ $? -ne 0 ]] && exit
echo END

$?是前一个命令的返回状态码。其中状态码0表示它正常退出,其他任何情况都是错误的。因此,我们使用&&的短路特性,以便在前一个命令失败时简洁地退出。
(See https://unix.stackexchange.com/questions/186826/parent-script-continues-when-child-exits-with-non-zero-exit-code了解更多信息)
注意:这将在任何类型的python失败时退出bash脚本,而不仅仅是ctrl+c,例如IndexError、AssertionError等

相关问题