为何trap在bash与sh中的执行表现存在差异?
脚本在bash正常处理SIGINT但sh中失效的原因及解决办法
问题描述
编写了如下脚本,意图在父进程收到SIGINT(Ctrl+C)时,通过trap向后台子进程发送SIGINT信号,子进程捕获该信号并打印提示:
#!/bin/bash first() { trap 'echo "INT first"' INT sleep 10 echo "slept" } trap 'echo "killing"; kill -INT "$PID"; echo "kill sent"' INT first & PID=$! echo "$PID" wait
在bash中执行时,行为符合预期:
$ ./test.sh 64257 ^Ckilling kill sent INT first slept
但将shebang改为#!/bin/sh后,子进程无法捕获SIGINT,父进程发送信号后无响应,需等待10秒才会打印slept:
$ ./test.sh 64550 ^Ckilling kill sent
原因分析
差异源于POSIX标准与bash扩展的信号处理规则不同:
- POSIX标准规定,非交互模式下的shell启动后台作业时,会自动将该作业的
SIGINT和SIGQUIT信号设为忽略状态。大多数系统的/bin/sh(如dash)严格遵循这一标准,因此你的后台子进程默认忽略SIGINT,即使父进程发送该信号,子进程也不会触发自定义trap,sleep会继续运行至结束。 - bash作为扩展,没有遵循这一POSIX规则,非交互模式下的后台作业不会自动忽略
SIGINT,因此子进程的自定义trap可以正常捕获信号。
解决方案
要让脚本在sh中正常工作,需在子进程中显式恢复SIGINT的默认处理,取消忽略状态,再设置自定义trap。修改后的脚本如下:
#!/bin/sh first() { # 恢复SIGINT的默认处理(取消shell自动设置的忽略状态) trap - INT # 设置自定义信号处理逻辑 trap 'echo "INT first"' INT sleep 10 echo "slept" } trap 'echo "killing"; kill -INT "$PID"; echo "kill sent"' INT first & PID=$! echo "$PID" wait
修改后在sh中执行,行为将与bash一致:按下Ctrl+C后,父进程触发trap发送信号,子进程捕获并打印INT first,sleep被中断后执行slept。
如果需要子进程收到SIGINT后直接退出(不打印slept),可以在自定义trap中添加exit:
trap 'echo "INT first"; exit' INT
内容的提问来源于stack exchange,提问作者Afkaaja
相关产品推荐
相关产品推荐

