健壮性:错误处理、退出码、trap 与模块引入
默认 shell 在命令失败时往往仍会往下执行,需要显式的错误处理策略。
一、退出码(Exit Code)
每个命令结束后都会返回一个 0–255 的退出码。
- 0:成功。
- 非 0:失败;具体含义依命令而定。
- 特殊变量
$?保存上一条命令的退出码。
shell
ls
echo "ls 退出码: $?"
ls non_existent_file.txt
echo "失败命令退出码: $?"二、set -e(失败即退出)
shell
set -e # 任一命令非 0 则终止脚本(errexit)优点:避免错误无声扩散。
缺点:在 if、while、&&、|| 等上下文中的行为需查阅手册;有些命令「失败」是你想捕获的,需要临时关闭或 cmd || true。
示例:
shell
set -e
echo "开始..."
ls
ls non_existent_file.txt # 若 set -e 生效,脚本在此退出
echo "这行可能执行不到"三、手动检查(最灵活)
推荐直接利用 if cmd 判断退出码,不必先跑再比 $?:
shell
if mkdir my_new_dir; then
echo "创建成功"
else
echo "创建失败"
exit 1
fi若沿用旧稿风格显式看 $? 也可:
shell
mkdir my_new_dir
if [ $? -eq 0 ]; then
echo "目录创建成功"
else
echo "错误:无法创建目录"
exit 1
fi四、trap:捕获信号与 EXIT
在脚本结束(正常、异常、中断)时做清理:删临时文件、恢复 stty 等。
shell
cleanup() {
echo -e "\n正在执行清理操作..."
# rm -rf "$TEMP_DIR"
echo "清理完毕。"
}
trap cleanup EXIT
echo "执行业务..."
sleep 2
ls non_existent_file.txt # 可能失败
# 无论前面如何,EXIT 时通常会尝试运行 cleanup(若 shell 未当场退出)常用信号:INT(Ctrl+C)、TERM、EXIT。具体行为与是否启用 set -e 有关,复杂脚本建议分段测试。
五、终止脚本:exit
使用 exit [状态码] 结束整个脚本。函数里的 exit 也会结束整个脚本;若只想结束函数,请用 return。
常见退出码约定
| 码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 通用错误 |
| 2 | Shell 内置误用等 |
| 126 | 命令不可执行(权限等) |
| 127 | 命令未找到 |
| 128 | exit 参数无效 |
| 128+n | 因信号 n 退出(如 130≈SIGINT) |
示例:
shell
#!/bin/bash
FILENAME="example.txt"
if [ ! -f "$FILENAME" ]; then
echo "错误:文件 $FILENAME 不存在。"
exit 1
fi
echo "文件存在,继续..."
exit 0注意:exit 只能传 0–255;过大数值会被取模。
六、引入外部脚本:source 与 .
要把另一个脚本里的 变量、函数 加载到当前 Shell,使用:
shell
source ./config.sh
. ./config.sh # 等价, POSIX 常用写法config.sh 示例
shell
APP_NAME="MyApplication"
VERSION="1.0.0"main.sh
shell
source ./config.sh
echo "Application Name: $APP_NAME"与「直接执行子脚本」的区别
source/.:当前进程中执行,共享变量与函数;不需要chmod +x。./other.sh或bash other.sh:多为子进程执行,默认不反向污染当前脚本的变量(除非显式导出等)。
注意事项
- 路径:生产环境尽量用绝对路径或基于脚本目录推算路径。
- 权限:被 source 的文件不需要执行权限。
- exit 陷阱:被引入的脚本若执行
exit,会导致整个当前脚本退出;公共库文件应避免随意exit,改为return(若在函数中)或由调用方判断。
七、推荐阅读顺序
语法层面的符号、正则见 [Shell 语法进阶:特殊符号与正则](./06-Shell 语法进阶:特殊符号与正则表达式.md);运维向范例见 运维脚本示例 子目录。
