🔍 登录 注册

Shell 脚本实战:自动化运维的必备利器

打 打工人 2026-10-20 运维 👁 1 ⏱ 5min

Shell 脚本实战:自动化运维的必备利器

在系统管理员的日常里,Shell 脚本是连接"手工操作"与"自动化平台"之间的桥梁。一个写得规范的脚本,能安全地完成批量部署、定时巡检、日志清理等重复劳动。本文从语法根基到生产实践,系统讲解 Shell 脚本的编写之道。

一、脚本的骨架:规范先行

#!/usr/bin/env bash
# 标题:日志清理脚本
# 作者:ops
# 日期:2026-10-20

set -euo pipefail   # 三条保命指令,见下文

LOG_DIR="/var/log/app"
RETENTION_DAYS=30

main() {
    if [[ ! -d "$LOG_DIR" ]]; then
        echo "错误: 日志目录不存在: $LOG_DIR" >&2
        exit 1
    fi
    find "$LOG_DIR" -type f -name "*.log" -mtime +"$RETENTION_DAYS" -delete
    echo "清理完成: 删除 $(find "$LOG_DIR" -type f -name "*.log" -mtime +"$RETENTION_DAYS" | wc -l) 个文件"
}

main "$@"

1.1 set -euo pipefail 的含义

  • set -e:任何命令失败立即退出,避免"失败后继续执行"的连锁错误;
  • set -u:使用未定义变量即报错,揪出拼写错误;
  • set -o pipefail:管道中任一环节失败,整条管道返回失败——否则 cmd | grep 只认 grep 的退出码。

这三条是生产脚本的底线。

二、变量、数组与引号的艺术

2.1 变量与默认值

env="${1:-production}"        # 参数缺省值
user="${USER:-unknown}"       # 环境变量缺省
readonly APP_PORT=8080        # 只读变量,防误改

2.2 数组与遍历

servers=("web-01" "web-02" "web-03")
for s in "${servers[@]}"; do
    ssh "$s" "systemctl status nginx" >/dev/null 2>&1 \
        && echo "$s OK" || echo "$s FAIL"
done

2.3 永远给变量加引号

# 反例:路径含空格时直接炸裂
rm -rf $path

# 正例
rm -rf "$path"

引号原则:所有变量展开都要加双引号,除非你明确需要分词(罕见)。

三、条件判断与函数

3.1 文件与数值判断

if [[ -f "$conf" ]] && [[ -r "$conf" ]]; then      # 文件存在且可读
    echo "配置可读"
fi

if (( disk_usage > 90 )); then                      # 数值比较用 (())
    echo "磁盘告警"
fi

[[ ]] 中的 -f/-d/-x/-z 分别对应"是文件/目录/可执行/为空",配合 &&、|| 组合成业务判断。

3.2 函数组织

log() { echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"; }
die() { log "FATAL: $*"; exit 1; }

脚本超过 50 行务必用函数分层:主函数 main 在最底部,被调用前定义。

四、实战:部署脚本的标准结构

#!/usr/bin/env bash
set -euo pipefail

APP_NAME="api"
RELEASE_DIR="/opt/$APP_NAME"
BACKUP_DIR="/var/backups/$APP_NAME"

backup() {
    local ts
    ts=$(date '+%Y%m%d%H%M%S')
    cp -a "$RELEASE_DIR" "$BACKUP_DIR/$APP_NAME-$ts"
    log "已备份到 $BACKUP_DIR/$APP_NAME-$ts"
}

deploy() {
    local pkg="$1"
    backup
    systemctl stop "$APP_NAME"
    tar -xzf "$pkg" -C "$RELEASE_DIR"
    systemctl start "$APP_NAME"
    sleep 3
    if systemctl is-active --quiet "$APP_NAME"; then
        log "部署成功"
    else
        log "启动失败,尝试回滚"
        rollback
    fi
}

main() {
    [[ $# -eq 1 ]] || die "用法: $0 <包路径>"
    deploy "$1"
}

main "$@"

这套结构包含了生产部署的必备要素:备份、幂等步骤、健康检查、失败回滚。

五、定时任务与日志

5.1 crontab 最佳实践

# 分 时 日 月 周
30 2 * * * /opt/scripts/backup_db.sh >> /var/log/cron/backup_db.log 2>&1

要点:cron 环境 PATH 极简,脚本内必须写全路径;输出重定向到专属日志,否则排错无从下手;关键任务加重试与锁。

5.2 防止重复执行:flock

exec 9>"$LOCK_FILE"
flock -n 9 || { echo "已有实例在运行"; exit 1; }

六、十个常见坑

  1. CRLF 换行符:Windows 编辑的脚本报 \r 错误,用 sed -i 's/\r$//' 清洗;
  2. if [ $x == 1 ] 单中括号:空变量直接报错,统一用 [[ ]];
  3. $? 判断太晚:两条命令之间夹了其他命令,$? 已失效,立即判断;
  4. cp 覆盖符号链接:cp 默认跟随链接,注意 -P 选项;
  5. find + rm 与空格:find ... | xargs rm 遇空格炸裂,改用 find ... -delete;
  6. 超时缺失:ssh、curl 卡死拖住整个脚本,一律加 timeout;
  7. 并发无锁:cron 与手动执行撞车,用 flock 上锁;
  8. 日志不落盘:脚本输出到终端被 cron 丢弃,必须重定向;
  9. 变量未引号:空格/通配符导致意外行为,见 2.3;
  10. set -e 与 grep 配合:grep 无匹配返回 1 触发退出,用 || true 或 if grep -q。

结语

Shell 脚本的价值在于把运维经验固化成可重复的执行路径。规范的三条保命指令、严谨的引号习惯、清晰的主函数结构、完备的备份回滚——这些纪律让脚本从"能跑"进化到"可靠"。当你的脚本库沉淀了部署、巡检、备份、清理四大类标准件,你就拥有了把几百台机器从"手忙脚乱"变成"按部就班"的能力。

读完了?给作者一点支持

本文采用 CC BY 4.0 协议转载 · © 打工人

评论(0)

登录 后参与评论。

  • 还没有评论,来抢沙发。

🔗 相关文章