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 版本管理,方便追溯和协作。
