1. 无条件开启 set -euo pipefail
这是新手脚本和可靠脚本最大的分水岭。三行选项各干一件事:
#!/usr/bin/env bash
set -euo pipefail
-e—— 任何命令失败立即退出,杜绝「一条命令挂了脚本还在跑」。-u—— 使用未定义变量即报错,提前暴露拼写错误。-o pipefail—— 管道中只要有一环失败,整体就判定失败,防止「grep 没匹配到但 echo 成功」。
例外
某些命令「预期会失败」时(如检查进程是否存在的
pgrep),
用 command || true 或 if command; then ... fi 显式处理,
而不是关掉 -e。
2. 参数校验与默认值
脚本要有「脾气」——参数不对就拒绝执行:
#!/usr/bin/env bash
set -euo pipefail
APP_ENV="${1:?用法: $0 }"
REGION="${REGION:-cn-north-1}" # 环境变量缺省值
REPLICAS="${REPLICAS:-3}"
case "$APP_ENV" in
dev|prod) ;;
*) echo "非法环境: $APP_ENV" >&2; exit 1 ;;
esac
${1:?...}—— 缺少必传参数时直接报错退出。${VAR:-default}—— 为空时用默认值,最常用的防御写法。case白名单校验,从源头拦截非法输入。
3. 统一的日志函数
输出带时间戳的日志,且把信息与错误分流到 stdout/stderr:
log() { echo "[$(date '+%F %T')] $*"; }
info() { log "[INFO] $*"; }
warn() { log "[WARN] $*" >&2; }
err() { log "[ERROR] $*" >&2; exit 1; }
info "开始部署 $APP_ENV"
warn "磁盘使用率偏高,请关注"
err "配置文件缺失"
好处立竿见影:脚本被 cron 或 CI 收集日志时,时间线一目了然;>&2
保证错误不会被误当正常输出重定向掉。
4. 并行执行与等待
运维里最常见的提速场景:多台机器同时执行同一任务。用 & 加 wait:
#!/usr/bin/env bash
set -euo pipefail
HOSTS=(10.0.0.1 10.0.0.2 10.0.0.3)
PIDS=()
for h in "${HOSTS[@]}"; do
( ssh "$h" 'systemctl restart app' && echo "[OK] $h" ) &
PIDS+=("$!")
done
# 逐个检查失败结果,避免 set -e 提前终止整个脚本
for pid in "${PIDS[@]}"; do
wait "$pid" || { echo "有主机执行失败 (pid=$pid)" >&2; FAILED=1; }
done
exit "${FAILED:-0}"
为什么每个子 shell 里加 set -e 的行为不同
子 shell 中失败会导致整个脚本提前退出,因此把「单个主机的成败」封装进子 shell,
用
wait 精确收集每个任务的退出码——这正是并行脚本的推荐写法。
5. 收尾:脚本框架模板
把以上技巧拼成一个可直接复用的骨架:
#!/usr/bin/env bash
set -euo pipefail
log() { echo "[$(date '+%F %T')] $*"; }
info() { log "[INFO] $*"; }
warn() { log "[WARN] $*" >&2; }
err() { log "[ERROR] $*" >&2; exit 1; }
APP_ENV="${1:?用法: $0 }"
cleanup() { info "清理临时文件"; }
trap cleanup EXIT
info "开始执行,环境: $APP_ENV"
# ... 业务逻辑 ...
info "执行完成"
最后提醒一条:脚本写好后一定跑一遍 bash -n script.sh 做语法检查,
再用 shellcheck
做静态分析——它能抓出大部分新手甚至老手都会犯的错误。好脚本和坏脚本的差别,
往往就在这些「微不足道」的细节里。
