python - 使用 KeyboardInterrupt 异常捕获 SIGINT 在终端中有效,而不是在脚本中有效

标签 python bash shell sigint

我试图在 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 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 for 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 session 中启动后台作业时忽略信号引起的,而不是由 python 直接引起的。至少 bashdash 是这样的,我没有尝试过其他 shell。

有两种选择来处理这种情况:

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

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

    #!/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

关于python - 使用 KeyboardInterrupt 异常捕获 SIGINT 在终端中有效,而不是在脚本中有效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40775054/

相关文章:

linux - 使用 rsync 观看失败,权限被拒绝,而 rsync 单独工作正常

bash - ffmpeg 从 10s 的视频中提取子剪辑; 30%; 60%;视频的最后 30 秒

shell - < 生成子 shell 时的文档在哪里

python - 通过bash脚本和pip在虚拟环境中安装python包

python - 如何使用 xlrd 将 Excel 文件读入 Python?它可以读取较新的 Office 格式吗?

python - 通过将函数应用于另一个数据框的列来创建新数据框

python - pandas系列的元素怎么简单的随心所欲的增加?

python - 文本框不会自动删除自身

bash - 如何将命令行参数传递给docker-compose run?

shell - 通过 Wscript.Shell Run 从 HTA 调用 MSG.exe