<!DOCTYPE html>
Linux Shell 脚本自动化运维实战:备份清理、存活监控与排错调试
运维干久了你会发现,80% 的日常操作是重复劳动:备份数据库、清理日志、检查网站是否挂、磁盘满了没……这些完全可以交给 Shell 脚本 + crontab 自动完成,把时间省下来干更有价值的事。本文不讲枯燥语法,直接给三个能立刻上线的实战脚本:日志备份清理、MySQL 自动备份、网站存活监控告警,并附上调试排错方法论,让脚本写得出来、跑得稳、坏了能查。
一、Shell 脚本基本功:写出规范脚本的 5 条铁律
先立规矩,脚本才能长期稳定运行:
1. 首行写 #!/bin/bash,明确解释器,避免用 sh 踩坑 2. 开头加 set -euo pipefail:出错即停、变量未定义即报错、管道失败即报错 3. 变量全部加引号,防止路径带空格时炸掉 4. 日志打点:每条关键步骤 echo 时间戳,出问题能定位到第几步 5. 先做前置检查(目录存在?命令存在?),再干正事
一个标准脚本骨架:
#!/bin/bash
set -euo pipefail
# 基础变量
BASE_DIR="/var/log/myapp"
LOG_FILE="/var/log/scripts/cleanup.log"
STAMP=$(date +%Y-%m-%d_%H%M%S)
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "${LOG_FILE}"
}
# 前置检查
[ -d "${BASE_DIR}" ] || { log "目录不存在: ${BASE_DIR}"; exit 1; }
log "脚本开始执行"
# ... 业务逻辑 ...
log "脚本执行完成"
二、实战脚本 1:日志备份清理脚本
需求:每天把应用日志按天归档,只保留最近 30 天,超过的删除,防止磁盘被日志塞满。
#!/bin/bash
set -euo pipefail
APP_LOG="/var/log/myapp/access.log"
ARCHIVE_DIR="/var/log/myapp/archive"
KEEP_DAYS=30
STAMP=$(date +%Y%m%d)
LOG_FILE="/var/log/scripts/log_cleanup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "${LOG_FILE}"
}
mkdir -p "${ARCHIVE_DIR}"
# 1. 把昨天的日志重命名归档(用 mv 比 copy+truncate 更省 IO)
if [ -f "${APP_LOG}" ]; then
mv "${APP_LOG}" "${ARCHIVE_DIR}/access_${STAMP}.log"
log "归档完成: access_${STAMP}.log"
fi
# 2. 通知应用重新打开日志文件(应用如支持 USR1 信号则最优雅)
# kill -USR1 $(cat /var/run/myapp.pid) 2>/dev/null || true
# 3. 删除超过保留天数的归档日志
find "${ARCHIVE_DIR}" -name "access_*.log" -mtime +${KEEP_DAYS} -type f -delete
log "已清理超过 ${KEEP_DAYS} 天的归档日志"
# 4. 统计磁盘占用并记录
du -sh "${ARCHIVE_DIR}" >> "${LOG_FILE}"
把这个脚本和 crontab 结合,每天凌晨执行:
crontab -e # 每天 00:30 执行 30 0 * * * /usr/local/bin/log_cleanup.sh
进阶提示:这一步其实也可以用 logrotate 完成(按大小/天切割、压缩、保留份数一行配置),但手工脚本的优势是"想干什么都行"——比如归档后还要 rsync 到异地,脚本里加一行即可。两者结合使用效果最好。
三、实战脚本 2:MySQL 自动备份脚本
需求:每天凌晨全量备份所有数据库,压缩后保留 7 天,并把备份同步到异地服务器,防止服务器挂了备份也没了。
#!/bin/bash
set -euo pipefail
DB_USER="backup"
DB_PASS="Backup@2026"
BACKUP_DIR="/backup/mysql"
KEEP_DAYS=7
REMOTE_HOST="backup@192.168.1.200"
REMOTE_DIR="/backup/mysql"
STAMP=$(date +%Y%m%d_%H%M%S)
LOG_FILE="/var/log/scripts/mysql_backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "${LOG_FILE}"
}
mkdir -p "${BACKUP_DIR}"
log "开始备份 MySQL"
# 1. 全量备份:single-transaction 保证备份期间数据一致且不锁表
# --routines/--triggers/--events 带上存储过程、触发器、事件,恢复才完整
mysqldump -u"${DB_USER}" -p"${DB_PASS}" \
--single-transaction --routines --triggers --events \
--databases --all-databases 2>/dev/null \
| gzip > "${BACKUP_DIR}/mysql_full_${STAMP}.sql.gz"
log "备份完成: mysql_full_${STAMP}.sql.gz"
# 2. 删除超过 7 天的本地备份
find "${BACKUP_DIR}" -name "mysql_full_*.sql.gz" -mtime +${KEEP_DAYS} -delete
# 3. 异地同步(关键!防止同机房故障/勒索病毒把备份一起带走)
# 用 rsync 增量传输,传完再删本地旧备份更稳妥
rsync -avz --timeout=300 "${BACKUP_DIR}/" "${REMOTE_HOST}:${REMOTE_DIR}/" >> "${LOG_FILE}" 2>&1 \
&& log "异地同步完成" || log "异地同步失败,请检查网络"
log "备份流程结束"
关键点:
1. --single-transaction 对 InnoDB 表用一致性快照,备份时不锁表,线上直接跑没压力 2. 先 rsync 到异地、确认成功后再删本地旧备份,避免"本地删了、异地也没传上去"的尴尬 3. 备份文件别只留一份,7 天轮换 + 异地副本,覆盖"误删、损坏、机房故障"三种场景
恢复演练(定期做,别等出事才想起来):
# 解压并导入 gunzip -c /backup/mysql/mysql_full_最新.sql.gz | mysql -uroot -p # 验证:查表数量、行数与生产对比,确认恢复可用 mysql -uroot -p -e "USE mydb; SELECT COUNT(*) FROM users;"
四、实战脚本 3:网站存活监控告警脚本
需求:每分钟检查网站首页是否正常返回 200,连续 3 次失败就发钉钉/飞书/微信告警,恢复后再通知。
#!/bin/bash
set -euo pipefail
URL="https://www.example.com/"
FAIL_FILE="/tmp/site_down_count"
NOTIFY_FILE="/tmp/site_notified"
WEBHOOK="https://oapi.dingtalk.com/robot/send?access_token=你的token"
# 检查一次
check() {
# -s 静默、-o /dev/null 丢弃响应体、-w 只输出状态码、--max-time 防卡死
code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${URL}" || echo "000")
echo "${code}"
}
notify() {
local msg="$1"
curl -s -H "Content-Type: application/json" -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"${msg}\"}}" "${WEBHOOK}" > /dev/null
}
code=$(check)
if [ "${code}" = "200" ]; then
# 网站恢复:清空失败计数;如果之前告警过,发一条恢复通知
if [ -f "${NOTIFY_FILE}" ]; then
notify "✅ 网站已恢复: ${URL}"
rm -f "${NOTIFY_FILE}"
fi
rm -f "${FAIL_FILE}"
else
# 记录连续失败次数
count=0
[ -f "${FAIL_FILE}" ] && count=$(cat "${FAIL_FILE}")
count=$((count + 1))
echo "${count}" > "${FAIL_FILE}"
if [ "${count}" -ge 3 ] && [ ! -f "${NOTIFY_FILE}" ]; then
notify "⚠️ 网站连续 ${count} 次不可用: ${URL} (HTTP ${code})"
touch "${NOTIFY_FILE}"
fi
fi
配合 crontab 每分钟执行:
* * * * * /usr/local/bin/site_monitor.sh
思路值得借鉴:连续失败 N 次才告警(避免单次网络抖动误报),用标记文件防止重复告警轰炸,恢复后自动通知。这类逻辑同样适用于端口检查(nc)、磁盘使用率检查(df)、进程检查(pgrep)。
五、排错与调试:脚本坏了怎么快速定位
1. 开启调试模式 set -x
最常用的调试手段:执行时加 bash -x,脚本会逐行打印执行过程,变量值一目了然:
bash -x /usr/local/bin/site_monitor.sh
# 输出形如:
# + code=$(curl -s -o /dev/null -w %{http_code} --max-time 10 https://www.example.com/)
# ++ curl -s -o /dev/null -w %{http_code} ...
# + [ 200 = 200 ]
也可以在脚本某段前后手动开关:
set -x # 打开调试 # ... 可疑代码 ... set +x # 关闭调试
2. 用 shellcheck 做静态检查
写脚本必备工具,能自动找出未加引号、可移植性、语法陷阱等几百种问题:
# 安装
apt install -y shellcheck
# 检查
shellcheck /usr/local/bin/mysql_backup.sh
# 输出形如:
# In mysql_backup.sh line 23:
# mysqldump -u"${DB_USER}" ...
# ^-- SC2086: Double quote to prevent globbing and word splitting.
3. 常见问题排查清单
1. 脚本手动执行成功、crontab 不执行 -> 大概率是 PATH 环境问题: crontab 环境 PATH 很精简,脚本里命令写绝对路径(/usr/bin/xxx)或开头 export PATH 2. 中文注释乱码 -> 文件编码必须是 UTF-8,且首行 #!/bin/bash 后无 BOM 3. 脚本里有\r 回车符报错 -> 在 Windows 编辑过,用 sed -i 's/\r$//' script.sh 去掉 4. 变量没加引号,路径带空格 -> "xxx" 包裹所有变量 5. 日志没输出 -> 检查日志目录权限,log 函数里的 >> 需要可写 6. 用 set -e 后"明明第2步失败了脚本还在跑" -> 确认是 set -euo pipefail 且管道各段都加了处理
4. 排错三板斧
1. bash -x 看执行过程 2. 看日志文件(脚本里 log() 留下的时间戳打点) 3. 手工拆分:把脚本复制一份,一段一段去掉,二分定位到具体语句
六、结语
三个脚本覆盖了运维最常碰的三件事:清日志、备数据库、盯存活。写脚本的核心不是记住每个语法,而是养成三个习惯:脚本规范化(set -euo pipefail + 日志打点)、关键操作留痕迹(日志文件 + 标记文件)、坏了有方法论(bash -x + shellcheck + 二分排除)。把重复劳动交给脚本,把精力留给真正需要判断力的事,这才是自动化的意义。




