Shell 脚本实战:运维自动化的必备技能与常用模板


阿里云特惠 - 新用户专享

Shell 脚本实战:运维自动化的必备技能与常用模板

Shell 脚本是运维自动化的核心工具。从简单的批量操作到复杂的部署流水线,Shell 无处不在。本文系统梳理 Shell 脚本在运维场景中的关键知识点,并提供可直接复用的常用模板。

一、脚本规范与最佳实践

#!/bin/bash
# 文件头三剑客:强制写上,避免奇怪的 bug
set -euo pipefail
# -e:任何命令返回非0则立即退出(避免错误被忽略)
# -u:使用未定义变量报错(避免拼写错误引发隐患)
# -o pipefail:管道中任意命令失败都视为失败

# 脚本信息注释
# 功能:每日 MySQL 备份
# 作者:ops-team
# 创建时间:2026-04-08

# 定义常量(大写)
readonly BACKUP_DIR="/backup/mysql"
readonly LOG_FILE="/var/log/mysql_backup.log"
readonly DATE=$(date +%Y%m%d_%H%M%S)

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}

log "开始执行备份脚本..."

二、常用语法速查

条件判断

# 文件/目录检查
if [ -f "/etc/nginx/nginx.conf" ]; then echo "文件存在"; fi
if [ -d "/backup" ]; then echo "目录存在"; fi
if [ ! -d "/backup" ]; then mkdir -p /backup; fi

# 字符串比较
if [ "$ENV" = "production" ]; then echo "生产环境"; fi
if [ -z "$DB_PASSWORD" ]; then echo "密码未设置!"; exit 1; fi
if [ -n "$DB_HOST" ]; then echo "主机已配置"; fi

# 数字比较
CPU=$(top -bn1 | awk '/Cpu/ {print int($2)}')
if [ "$CPU" -gt 80 ]; then echo "CPU 使用率过高: ${CPU}%"; fi

循环

# for 循环(批量操作)
for server in 192.168.1.{10..20}; do
    echo "检查服务器: $server"
    ping -c 1 -W 2 "$server" &>/dev/null && echo "$server: UP" || echo "$server: DOWN"
done

# while 循环(读取文件每一行)
while IFS= read -r line; do
    echo "处理: $line"
done < /etc/servers.txt

# 带重试的循环
MAX_RETRY=3
for i in $(seq 1 $MAX_RETRY); do
    curl -sf https://api.example.com/health && break
    echo "第 $i 次失败,等待5秒重试..."
    sleep 5
done

函数

# 函数定义和使用
backup_database() {
    local DB_NAME="$1"
    local DEST="$2"
    log "开始备份数据库: $DB_NAME"
    mysqldump -u root -p"${DB_PASS}" --single-transaction "$DB_NAME"         | gzip > "${DEST}/${DB_NAME}_${DATE}.sql.gz"
    local RESULT=$?  # 获取上条命令的退出码
    if [ $RESULT -eq 0 ]; then
        log "备份成功: ${DB_NAME}"
    else
        log "备份失败: ${DB_NAME} (退出码: $RESULT)"
        return 1
    fi
}

# 调用函数
backup_database "app_db" "$BACKUP_DIR"
backup_database "user_db" "$BACKUP_DIR"

三、实用运维脚本模板

1. 服务健康检查脚本

#!/bin/bash
set -euo pipefail

SERVICES=("nginx" "mysql" "redis")
ALERT_EMAIL="ops@example.com"

for svc in "${SERVICES[@]}"; do
    if ! systemctl is-active --quiet "$svc"; then
        echo "[ALERT] 服务 $svc 已停止,尝试重启..." |             mail -s "服务故障告警: $svc" "$ALERT_EMAIL"
        systemctl restart "$svc" && echo "$svc 重启成功" || echo "$svc 重启失败!"
    fi
done
echo "所有服务检查完毕"

2. 磁盘空间告警脚本

#!/bin/bash
THRESHOLD=80  # 使用率超过 80% 触发告警
ALERT_EMAIL="ops@example.com"
HOSTNAME=$(hostname)

while IFS= read -r line; do
    USAGE=$(echo "$line" | awk '{print $5}' | tr -d '%')
    MOUNT=$(echo "$line" | awk '{print $6}')
    if [ "$USAGE" -gt "$THRESHOLD" ]; then
        echo "磁盘告警: $HOSTNAME $MOUNT 使用率 ${USAGE}%" |             mail -s "磁盘空间告警" "$ALERT_EMAIL"
    fi
done < <(df -h | grep -v "^Filesystem" | grep -v "tmpfs")

3. 批量 SSH 执行命令

#!/bin/bash
SERVERS_FILE="/etc/ops/servers.txt"  # 每行一个服务器 IP
COMMAND="$1"  # 接受命令作为参数
SSH_KEY="~/.ssh/id_rsa"
SSH_USER="ubuntu"

if [ -z "$COMMAND" ]; then
    echo "用法: $0 '要执行的命令'"
    exit 1
fi

while IFS= read -r SERVER; do
    echo "=== 执行于 $SERVER ==="
    ssh -i "$SSH_KEY" -o StrictHostKeyChecking=no         -o ConnectTimeout=5 "${SSH_USER}@${SERVER}" "$COMMAND" 2>&1 ||         echo "  [ERROR] 连接 $SERVER 失败"
done < "$SERVERS_FILE"

4. 日志清理脚本

#!/bin/bash
LOG_DIRS=("/var/log/nginx" "/var/log/mysql" "/opt/app/logs")
RETENTION_DAYS=30

for dir in "${LOG_DIRS[@]}"; do
    if [ -d "$dir" ]; then
        COUNT=$(find "$dir" -name "*.log*" -mtime +$RETENTION_DAYS | wc -l)
        find "$dir" -name "*.log*" -mtime +$RETENTION_DAYS -delete
        echo "清理 $dir:删除 $COUNT 个日志文件"
    fi
done

# 强制 nginx 重新打开日志文件(日志切割)
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)

四、错误处理与通知

#!/bin/bash
# trap 捕获错误,脚本异常退出时发送告警
trap 'error_handler $LINENO' ERR

error_handler() {
    local LINE="$1"
    echo "[ERROR] 脚本在第 $LINE 行发生错误" |         mail -s "运维脚本异常" ops@example.com
    exit 1
}

总结

好的 Shell 脚本应该像好的代码一样:有注释、有错误处理、有日志输出。掌握 set -euo pipefail、函数封装、trap 错误捕获这三个习惯,你的脚本就能从"能跑"升级为"可信赖的生产级工具"。所有运维脚本应纳入 Git 版本管理,方便追溯和协作。

发表评论