Appearance
16|日常运维脚本实例
前面学的语法、工具、错误处理,组合起来能写实用的运维脚本。本篇给几个真实会用到的脚本——巡检、备份、批量操作。每个脚本都按上一篇的生产脚本结构来写,能直接用或稍改即用。
一、巡检脚本
收集一组机器的基本状态,汇总成报告。这是值班最常见的"快速看一眼"工具。
bash
#!/usr/bin/env bash
set -euo pipefail
hosts=${1:-web-01 web-02 db-01}
report="/tmp/inspect-$(date +%Y%m%d-%H%M%S).txt"
log() { echo "[$(date '+%F %T')] $*"; }
log "开始巡检,目标: ${hosts}"
{
echo "=== 巡检报告 $(date) ==="
echo
for host in $hosts; do
echo "--- $host ---"
if ssh -o ConnectTimeout=5 "$host" "
echo \"主机名: \$(hostname)\";
echo \"运行时间: \$(uptime)\";
echo \"磁盘:\";
df -h | grep -vE 'tmpfs|devtmpfs';
echo \"内存:\";
free -h;
echo \"失败服务:\";
systemctl --failed --no-pager 2>/dev/null || echo 无;
" 2>/dev/null; then
echo " (采集完成)"
else
echo " !! 连不上 $host"
fi
echo
done
} | tee "$report"
log "报告已生成: $report"要点:
set -euo pipefail安全开头- 远程命令用
ConnectTimeout=5防止卡死 tee既显示又存文件- 连不上的机器不中断,记
!!标记 - 远程命令里的
$()要转义成\$(...),让远程 shell 展开
二、日志备份脚本
备份应用日志,按日期归档,保留最近 N 天。
bash
#!/usr/bin/env bash
set -euo pipefail
src_dir=${1:?用法: $0 <日志目录> [保留天数]}
keep_days=${2:-30}
backup_dir="/data/backup/logs"
date=$(date +%F)
log() { echo "[$(date '+%F %T')] $*" >&2; }
[[ -d "$src_dir" ]] || { log "错误: $src_dir 不是目录"; exit 1; }
mkdir -p "$backup_dir"
archive="${backup_dir}/$(basename "$src_dir")-${date}.tar.gz"
log "备份 $src_dir 到 $archive"
if tar -czf "$archive" -C "$(dirname "$src_dir")" "$(basename "$src_dir")"; then
log "备份成功: $(du -h "$archive" | cut -f1)"
else
log "备份失败"
exit 1
fi
# 清理过期备份
log "清理 ${keep_days} 天前的备份"
find "$backup_dir" -name "*.tar.gz" -mtime +"$keep_days" -delete
log "剩余备份: $(ls "$backup_dir" | wc -l) 个"要点:
- 参数校验
${1:?...}和目录检查 tar -C 目录 文件名避免绝对路径导致 tar 报错- 备份成功后用
du显示大小 find -mtime +N -delete清理过期文件,放 cron 里定期跑
放进 cron 每天跑:
bash
0 2 * * * /usr/local/sbin/backup-logs.sh /var/log/myapp 30三、批量执行脚本
对一组机器执行同一命令,收集结果。
bash
#!/usr/bin/env bash
set -euo pipefail
cmd=${1:?用法: $0 <命令> [主机...]}
shift
hosts=("$@")
[[ ${#hosts[@]} -gt 0 ]] || { echo "错误: 没有指定主机" >&2; exit 1; }
ok=0
fail=0
for host in "${hosts[@]}"; do
if ssh -o ConnectTimeout=5 "$host" "$cmd" </dev/null; then
echo "[OK] $host"
((ok++))
else
echo "[FAIL] $host" >&2
((fail++))
fi
done
echo "成功 $ok 台,失败 $fail 台"
[[ $fail -eq 0 ]] || exit 1要点:
- 命令和主机分离,第一个参数是命令,剩下是主机
</dev/null防止 ssh 卡在等输入- 逐台执行,成功失败分别计数
- 有失败时退出码非 0,让调用方能判断
- 输出
[OK]/[FAIL]前缀,方便 grep
用法:
bash
./batch-exec.sh "systemctl restart nginx" web-01 web-02 web-03
./batch-exec.sh "df -h /" $(cat hosts.txt)四、磁盘告警检查
检查磁盘使用率,超过阈值发告警。可放 cron 定期跑。
bash
#!/usr/bin/env bash
set -euo pipefail
threshold=${1:-80}
alert_email=${ALERT_EMAIL:-}
log() { logger -t disk-check "$*"; echo "$*" >&2; }
while read -r _ _ usage _ mount; do
usage=${usage%\%}
[[ "$usage" =~ ^[0-9]+$ ]] || continue
[[ "$mount" =~ ^(tmpfs|devtmpfs|/dev/)$ ]] && continue
if (( usage >= threshold )); then
log "告警: $mount 使用率 ${usage}% 超过 ${threshold}%"
[[ -n "$alert_email" ]] && \
echo "$mount 使用率 ${usage}%" | mail -s "磁盘告警 $mount" "$alert_email"
fi
done < <(df -h)
log "检查完成"要点:
df -h输出逐行处理,read拆字段usage%\%用参数扩展去掉百分号- 跳过 tmpfs 等非真实磁盘
logger写进 syslog 方便集中查- 可选邮件告警(环境变量控制)
放进 cron 每 10 分钟检查:
bash
*/10 * * * * ALERT_EMAIL=ops@xxx /usr/local/sbin/disk-check.sh 85五、服务健康检查
检查一组服务是否正常,异常就尝试重启。
bash
#!/usr/bin/env bash
set -euo pipefail
services=(nginx mysqld redis)
log() { echo "[$(date '+%F %T')] $*"; }
for svc in "${services[@]}"; do
if systemctl is-active --quiet "$svc"; then
log "$svc 正常"
else
log "$svc 异常,尝试重启" >&2
if systemctl restart "$svc"; then
sleep 2
if systemctl is-active --quiet "$svc"; then
log "$svc 重启成功"
else
log "$svc 重启后仍异常,需人工介入" >&2
fi
else
log "$svc 重启失败" >&2
fi
fi
done要点:
- 服务列表用数组,增删改一处
systemctl is-active --quiet静默判断状态- 重启后再检查一次,确认真的恢复了
- 重启失败不放弃,记录后继续检查下一个
六、脚本通用要点
这几个脚本有共同的模式:
- shebang + set:每个脚本开头都是
#!/usr/bin/env bash+set -euo pipefail - 参数校验:必填参数用
${1:?...},目录文件用[ -d ]检查 - 日志函数:定义
log()函数统一输出格式 - 错误进 stderr:
>&2让错误和正常输出分开 - 退出码:成功 0,失败非 0,让 cron 和调用方能判断
- 可配置:阈值、保留天数用参数或环境变量,不写死
写自己的运维脚本时套这个模式,脚本能跑、能维护、出错能查。
学会了之后
巡检、备份、批量执行、告警、健康检查——这几个脚本覆盖了日常运维的高频需求。把它们改成自己的环境(主机列表、目录路径、服务名),就能直接用。模式记住:安全开头、参数校验、日志函数、错误处理、退出码。
下一篇讲一个更复杂的实战——用 Shell 写幂等的初始化脚本。