Bash 脚本基础
只要一个动作要重复三次,就值得考虑写脚本。
生产级脚本标准结构
#!/usr/bin/env bash
# 脚本说明:部署 myapp 到生产服务器
# 用法:./deploy.sh [tag]
# 输入:Git tag(默认 main)
# 输出:部署日志写入 /var/log/myapp/deploy.log
# 失败:退出码非 0,标准错误输出到 stderr
set -euo pipefail
trap 'echo "[ERROR] 脚本在第 $LINENO 行失败" >&2' ERR
# === 常量 ===
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_DIR=/var/log/myapp
readonly TAG="${1:-main}"
# === 日志函数 ===
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $*" | tee -a "$LOG_DIR/deploy.log"; }
warn() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARN] $*" | tee -a "$LOG_DIR/deploy.log" >&2; }
err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $*" | tee -a "$LOG_DIR/deploy.log" >&2; }
# === 参数检查 ===
if [[ -z "$TAG" ]]; then
err "TAG 不能为空"
exit 1
fi
log "开始部署 tag=$TAG"
# ... 部署逻辑
log "部署完成"
关键安全选项
| 选项 | 作用 | 没有它会发生什么 |
|---|---|---|
set -e | 命令非零退出时立即中止脚本 | 错误后继续执行,可能破坏系统状态 |
set -u | 使用未定义变量时报错 | $VAIRABLE 拼写错误被忽略,产生难以追踪的 bug |
set -o pipefail | 管道任一环节失败即失败 | false \| true 整体返回 0,掩盖错误 |
trap ... ERR | 在任意命令失败时执行清理 | 临时文件、锁文件等资源泄漏 |
变量与引用规范
# ✅ 正确:始终引用变量(防止空格/特殊字符问题)
file="my file.txt"
cp "$file" /backup/
# ❌ 错误:未引用,空格导致分词
cp $file /backup/ # 等价于 cp my file.txt /backup/,报错
# ✅ 正确:带默认值的变量
APP_ENV="${APP_ENV:-production}"
# ✅ 正确:只读常量
readonly DB_HOST=db.internal
# ✅ 正确:命令替换
NOW=$(date +%Y%m%d)
DIR="$(dirname "$(realpath "$0")")"
函数与错误处理模式
#!/usr/bin/env bash
set -euo pipefail
# 判断命令是否存在
require_cmd() {
command -v "$1" &>/dev/null || { echo "缺少命令: $1"; exit 1; }
}
# 带重试的 curl(网络抖动场景)
fetch_with_retry() {
local url=$1 max_attempts=3 attempt=1
while (( attempt <= max_attempts )); do
curl -sf "$url" && return 0
warn "第 $attempt 次请求失败,${attempt}s 后重试..."
sleep "$attempt"
(( attempt++ ))
done
err "请求失败,已重试 $max_attempts 次: $url"
return 1
}
require_cmd curl
require_cmd jq
fetch_with_retry "https://api.example.com/health"
常见陷阱速查
| 陷阱 | 错误写法 | 正确写法 |
|---|---|---|
| 未引用的变量 | rm $DIR/* | rm "$DIR"/* |
比较用 = vs == | if [ $a = $b ] | if [[ "$a" == "$b" ]] |
| 检查文件存在 | if [ -e $file ] | if [[ -f "$file" ]] |
| 算术比较 | if [ $n > 5 ] | if (( n > 5 )) |
| 子 shell 变量泄漏 | $(cmd) 内修改的变量在外部无效 | 用临时文件或管道传递结果 |
cd 失败后继续执行 | cd /tmp; rm -rf * | cd /tmp || exit 1; rm -rf * |
本节执行清单
- [ ] 所有新脚本加上
set -euo pipefail和trap - [ ] 用
log()函数统一输出格式(含时间戳和级别) - [ ] 变量始终加引号,常量用
readonly - [ ] 用
command -v在脚本开头检查依赖命令是否存在
下一节:定时任务与巡检脚本——让脚本自动运行起来。