Shell 脚本别图快,这几个习惯能救你
# Shell 脚本别图快,这几个习惯能救你
几年前我写过一条清理脚本,大概是这样的:
```bash
rm -rf $TARGET_DIR/*
```
平时跑没事。有一次变量因为前一步失败空了,展开成了 `rm -rf /*`。好在当时是在测试机,而且 `rm` 的保护拦住了一部分,但那一刻我后背是凉的。从那以后,我写 shell 都会先把「别再出这种事」放第一位。
## 第一件事:打开严格模式
脚本开头加这一行,能挡掉大部分低级错误:
```bash
set -euo pipefail
```
拆开看:
- `-e`:任何命令失败就退出,不会带着错误继续跑
- `-u`:用到未定义变量就报错,专治上面那种空变量事故
- `-o pipefail`:管道里任何一个命令失败,整条管道算失败
很多人只加 `-e`,但管道默认只看最后一个命令,`cmd1 | cmd2` 里 cmd1 挂了不一定暴露。加上 pipefail 才完整。
## 第二件事:变量永远加引号
```bash
# 可能出问题
cp $SRC $DST
# 安全
cp "$SRC" "$DST"
```
变量里有空格或者为空时,不加引号会被拆成多个参数,甚至变成空参数。路径尤其容易踩。
数组也一样:
```bash
for f in "${files[@]}"; do
echo "$f"
done
```
## 第三件事:删除前先确认
```bash
: "${TARGET_DIR:?TARGET_DIR is required}"
if [ ! -d "$TARGET_DIR" ]; then
echo "target not found: $TARGET_DIR" >&2
exit 1
fi
rm -rf "${TARGET_DIR:?}/"*
```
`${VAR:?message}` 的意思是变量为空就报错退出,这是防止空变量最直接的办法。再配一个目录存在性检查,双重保险。
## 第四件事:用 trap 清理临时资源
脚本中途失败,临时文件、锁、后台进程要收拾干净:
```bash
TMP_DIR=$(mktemp -d)
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
```
不管正常结束还是出错,EXIT 都会触发。比在每个分支里手写清理靠谱。
## 第五件事:写日志,别全靠 echo
```bash
log() {
echo "[$(date '+%F %T')] $*" >&2
}
log "start sync"
```
统一格式、带时间戳、输出到 stderr。排查问题的时候,时间线比散落的 echo 有用得多。
## 第六件事:检查依赖命令
脚本依赖的工具不存在,跑一半才报错很尴尬:
```bash
for cmd in curl jq tar; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "missing: $cmd" >&2
exit 1
}
done
```
开头检查一遍,比中途失败好定位。
## 一个相对完整的骨架
```bash
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log() { echo "[$(date '+%F %T')] $*" >&2; }
main() {
: "${TARGET:?TARGET is required}"
log "target=$TARGET"
# 实际逻辑
}
main "$@"
```
用函数包起来,变量作用域清楚,也方便测试单个函数。
## 几个容易忽略的点
- `cd` 失败要处理,别假设一定成功
- 循环里读文件用 `while IFS= read -r line`,别用 `for line in $(cat)`
- 数值比较用 `(( ))`,字符串比较用 `[[ ]]`
- 尽量用 `#!/usr/bin/env bash`,别写死 `/bin/bash`
## 小结
Shell 脚本写起来快,但它的容错全靠你自己补。严格模式、变量加引号、删除前确认、trap 清理,这四条能挡掉绝大多数事故。剩下的就是日志和依赖检查,让排查更快。
脚本是给人省事的,别让它变成事故源头。花十分钟加这些防护,比出事后再改划算得多。
- 点赞
- 收藏
- 关注作者
评论(0)