(拍大腿)哎我说各位,有没有遇到过这种情况——写了个牛哄哄的脚本,运行到一半突然退出了,留你在风中凌乱?上周我徒弟写的自动备份脚本,就因为退出方式不对,把整个硬盘给清空了!今儿咱就掰开了揉碎了聊聊,这shell脚本到底该怎么优雅退出!
一、为啥要关心退出方式?
先整明白个事儿,shell脚本退出可不是敲个Ctrl+C那么简单。去年某公司数据库迁移脚本,因为没正确处理退出状态,导致1TB数据损坏。三个必须知道的真相:
- 退出状态码影响后续流程(0表示成功,非0都是异常)
- 未清理的临时文件会堆积成山(/tmp目录爆满可不是闹着玩的)
- 信号处理不当可能导致僵尸进程(见过CPU占用100%的脚本鬼魂吗?)
举个真实案例,我去年写的日志分析脚本,忘记在退出时关闭文件描述符,结果把32G内存吃光了——服务器直接宕机两小时!
二、基础退出姿势三连击 1. exit命令怎么用才专业?
记住这个口诀:0表成功非0错,状态码要按规矩来。举个栗子:
bash复制#!/bin/bash if [ ! -f "data.txt" ]; then echo "文件不存在" >&2 exit 1 fi # 正常操作 exit 0
重点来了:千万别学某些教程用exit 2表示警告,标准约定1-255都是错误,具体数字最好自己定义个文档说明。
2. return和exit有啥区别?(挠头)这个问题连老鸟都常搞混。简单说:
- return 用于函数返回
- exit 直接结束整个脚本
看个对比案例:
bash复制3. 陷阱信号怎么处理?function test() { return 5 echo "这行不会执行" } test echo $? # 输出5 exit 3 echo "这行也不会执行"
教你们个绝活——trap命令。上周我写的下载脚本,就靠这个实现了CTRL+C时的断点续传:
bash复制trap 'cleanup; exit 130' SIGINT cleanup() { echo "正在保存进度..." rm -f tmp_* }
记住常见信号:
- SIGINT(2): Ctrl+C
- SIGTERM(15): 默认kill信号
- SIGHUP(1): 终端断开
三、进阶操作五重奏 1. 如何优雅清理资源?
(敲黑板)这个太重要了!推荐使用finally模式:
bash复制2. 嵌套脚本怎么退出?finally() { # 关闭数据库连接 # 删除临时文件 # 释放内存资源 } trap finally EXIT
遇到调用其他脚本的情况,记得检查$?:
bash复制3. 后台进程怎么处理?./sub_script.sh if [ $? -ne 0 ]; then echo "子脚本跪了" >&2 exit 100 fi
(摸下巴)这个坑我踩过!一定要收尾:
bash复制4. 超时自动退出怎么搞?python worker.py & pid=$! trap "kill $pid; exit" EXIT
用timeout命令最省事:
bash复制5. 日志记录怎么不丢失?timeout 300 ./long_script.sh || { echo "超时强制退出" exit 201 }
推荐使用tee命令双写日志:
bash复制exec > >(tee -a output.log) exec 2>&1
四、常见作死行为黑名单 1. 千万不要这样退出!
- 直接kill -9(会导致资源无法释放)
- 多层嵌套只用exit(可能跳过清理步骤)
- 忽略错误代码(echo $?很重要!)
- 在trap里用exit不带状态码
- 重复捕获同一个信号
- 在trap处理函数里执行耗时操作
见过最蠢的操作:
bash复制tmpfile=$(mktemp) exit 0 # 临时文件永远留在系统里
正确姿势应该放在trap里清理!
个人观点时间
(伸懒腰)干了十年运维,见过太多脚本退出引发的惨案。我的原则是:宁可多写三行清理代码,也不留一个隐患。最近发现个新趋势,用Go重写关键shell脚本能避免很多退出问题,不过学习成本有点高。你们有啥独门绝技?评论区唠唠呗!不过说好了啊,发现好用的套路可别藏着掖着~