脚本运行后不退出通常是因为存在后台任务、标准输入被占用或文件描述符未关闭。优先检查是否有挂起的子进程或等待输入的操作,以下是具体排查与修复方案。
先说结论:大多数情况是后台进程未等待或标准输入未关闭导致的,需按优先级排查。
- 先确认:是否有后台任务(&)未执行 wait 或标准输入被占用
- 先处理:补充 wait 命令或重定向标准输入到 /dev/null
- 再验证:观察脚本是否正常返回退出码且进程消失
典型故障场景与修复对比
场景一:后台任务未等待
在循环中启动后台任务但未等待其结束,主脚本会一直等待所有子任务完成。
错误示例:
#!/bin/bash
for i in {1..5}; dosleep 10 & # 后台运行,但未管理
done
# 脚本在此处不会退出,直到所有 sleep 完成
echo "Done"
修复示例:
#!/bin/bash
for i in {1..5}; dosleep 10 &
done
wait # 显式等待所有后台任务结束
echo "Done"
场景二:标准输入被占用
脚本继承了对终端的标准输入,若子进程或脚本本身尝试读取输入但无数据,会导致挂起。
错误示例:
#!/bin/bash
# 某些命令可能隐式读取 stdin
some_command &
# 脚本退出时,若 stdin 未关闭,可能阻塞
修复示例:
#!/bin/bash
# 重定向标准输入为空,防止继承占用
exec 0<&- # 关闭标准输入
# 或者在启动时重定向
some_command </dev/null &
wait
深度排查命令
当脚本挂起时,无法在脚本内部执行命令,需在另一个终端操作。
1. 查找挂起进程 PID:
pgrep -f script.sh
# 或
ps -ef | grep script.sh
2. 查看文件描述符(在另一个终端执行):
# 将 <PID> 替换为实际进程号
lsof -p <PID> | grep REG
# 重点关注是否有未关闭的文件或 socket
3. 追踪系统调用(定位阻塞点):
# 查看进程卡在哪个系统调用上
strace -p <PID>
# 常见卡在 read 或 wait4 调用
验证是否生效
1. 观察提示符:脚本运行结束后,命令行提示符应立即返回。
2. 检查退出码:
./script.sh
echo $? # 应为 0 或其他预期退出码
3. 确认进程消失:
ps -ef | grep script.sh # 应无相关进程实例
常见坑
1. 循环中的后台任务:在 for 循环中频繁启动后台进程但未 wait,会导致进程堆积且脚本不退出。
2. 网络请求超时:使用 curl 或 wget 时未设置超时参数(如 `--connect-timeout`),网络阻塞会导致脚本无限等待。
3. 管道阻塞:生产者 - 消费者模型中,如果读取端退出而写入端继续写,管道缓冲区满后写入端会阻塞。
4. 误区提醒:不要仅依赖 nohup 或 disown 来规避脚本不退出问题,这掩盖了资源未释放的根本原因,应修复脚本逻辑。
原文链接:https://www.zjcp.cc/ask/11053.html
