非交互式脚本中应通过trap捕获SIGINT/SIGTERM/HUP/EXIT信号实现优雅退出与幂等清理,而非依赖input中断或read异常处理。

在非交互式脚本中,标准输入中断(如 Ctrl+C)其实不会直接触发 KeyboardInterrupt——因为 input() 这类阻塞读取在非交互式环境中通常根本不会执行,或会立即失败。真正需要关注的,是脚本本身被外部信号中断(比如用户手动 kill -INT $pid 或前台运行时按 Ctrl+C),以及脚本内部调用可能读取 stdin 的命令(如 read、cat、awk 等)被中断后的资源残留问题。
核心原则:别依赖 stdin 中断,要捕获信号中断
非交互式脚本默认没有终端控制权,也不监听键盘事件。所谓“标准输入中断异常”,实质是进程收到了 SIGINT(2)、SIGTERM(15)或意外错误导致提前退出。优雅处理的关键不是拦截某个“输入异常”,而是用 trap 在信号到来或脚本退出时自动清理。
-
trap是唯一可靠机制,read命令被Ctrl+C中断时会返回非零退出码,但不会抛出异常;靠set -e或if判断不足以覆盖所有中断路径 - 不要在非交互脚本里写
input或等待用户键入——它大概率卡死或报read: read error: 0: Resource temporarily unavailable - 如果脚本确实需从 stdin 读数据(如管道输入:
echo "data" | ./script.sh),应假设输入可能被上游中断,重点保障自身退出时的幂等清理
用 trap 捕获 SIGINT/SIGTERM 并清理资源
这是最常用也最稳妥的做法。定义一个清理函数,在收到中断信号时执行,然后退出。
- 先创建临时文件或目录,并记录路径(例如
tmpdir=$(mktemp -d)) - 在脚本开头立即注册 trap:
trap 'rm -rf "$tmpdir"; exit 1' INT TERM - 确保
tmpdir变量在 trap 作用域内可访问(避免子 shell 或局部变量导致未定义) - 若需兼容后台运行场景,可额外加
HUP:trap 'rm -rf "$tmpdir"; exit 1' INT TERM HUP
配合 EXIT trap 实现无条件兜底清理
无论正常结束还是被杀掉,EXIT trap 都会执行。把它和状态判断结合,能兼顾健壮性与语义清晰。
- 写清理函数(如
cleanup() { rm -f "$tmpfile"; kill "$child_pid" 2>/dev/null; }) - 注册:
trap cleanup EXIT(注意:不带引号的函数名更安全,避免变量扩展时机问题) - 关键点:清理动作本身要是幂等的,比如
rm -f、kill -0检查再杀、unset变量等,重复执行不报错 - 避免在 trap 中依赖
$?判断退出原因——它在 EXIT trap 里不可靠;如需区分,可用自定义变量标记:exit_code=0; ... || { exit_code=1; },再在 cleanup 中检查
对 read 命令做防御性处理
当脚本显式使用 read 读取 stdin(例如解析管道输入),需防止因中断导致后续逻辑错乱。
-
read收到SIGINT会立即返回退出码 130(即 128+2),可用if ! read line; then case $? in 130) echo "Interrupted, cleaning up..."; cleanup; exit 1;; esac; fi - 更简洁做法:统一用
trap拦截信号,让read自然中断,由 trap 承担全部清理责任,主流程无需层层判断 - 若读取超时敏感,可加
-t选项:if ! read -t 5 line; then echo "Timeout or interrupt"; cleanup; exit 1; fi











