服务器磁盘被打满的原因,九成是日志没人管。写一个能定时跑、超阈值会叫人的脚本,比事后救火体面得多。
需求拆解
- 按时间清理 N 天前的日志
- 磁盘使用率超过阈值时告警
- 找到并处理异常大的单个文件
- 全程记日志,方便事后追溯
脚本
#!/bin/bash
# disk_clean.sh —— 日志清理与磁盘巡检
set -uo pipefail
LOG_DIRS=("/var/log/myapp" "/data/logs")
KEEP_DAYS=14
WARN_PCT=80
CRIT_PCT=90
LOG_FILE="/var/log/disk_clean.log"
log() { echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"; }
alert() {
local level="$1" msg="$2"
log "[$level] $msg"
# 接入实际告警渠道,如企业微信 / 钉钉 / 邮件
curl -s -X POST "$WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"[$level] $(hostname): $msg\"}}" \
>/dev/null 2>&1 || true
}
# 1. 清理过期日志
for d in "${LOG_DIRS[@]}"; do
[ -d "$d" ] || continue
before=$(du -sm "$d" 2>/dev/null | cut -f1)
find "$d" -type f -name "*.log*" -mtime +$KEEP_DAYS -delete 2>/dev/null
after=$(du -sm "$d" 2>/dev/null | cut -f1)
log "清理 $d: ${before}MB -> ${after}MB"
done
# 2. 揪出异常大的文件(超过 500MB 单独点名)
find "${LOG_DIRS[@]}" -type f -size +500M 2>/dev/null | while read -r f; do
alert WARN "大文件: $f ($(du -h "$f" | cut -f1))"
done
# 3. 磁盘水位检查
df -P | awk 'NR>1 {print $5, $6}' | while read -r pct mount; do
n=${pct%\%}
[ "$n" -ge "$CRIT_PCT" ] && alert CRIT "磁盘 $mount 使用率 ${n}%"
[ "$n" -ge "$WARN_PCT" ] && [ "$n" -lt "$CRIT_PCT" ] && \
alert WARN "磁盘 $mount 使用率 ${n}%"
done
log "巡检完成"
几个容易忽略的细节
-mtime +N 是严格大于 N 天,不是”N 天前至今”。要保留 14 天,写 -mtime +14。
先压后删更省空间:如果日志有保留要求,先 gzip 再删原文:
find "$d" -name "*.log" -mtime +7 -exec gzip {} \;
find "$d" -name "*.log.gz" -mtime +30 -delete
set -e 慎用:清理类脚本里单个 find 失败不该让整个脚本退出,用 set -uo pipefail 而不加 -e。
while read 管道里的变量:df | while read 在子 shell 里执行,循环内修改的变量外面读不到。本例只调用函数不修改变量,所以没问题;要累计计数就得用进程替换 while read ...; done < <(df -P)。
挂到定时任务
# /etc/cron.d/disk_clean
30 3 * * * root /usr/local/bin/disk_clean.sh
凌晨 3 点半跑,避开业务高峰,也不和常见的整点任务抢 IO。
验证清单
- 造几个假文件,
touch -d "20 days ago"验证能被正确清理 - 把阈值临时改成 1,确认告警真能发出来
- 确认
WEBHOOK_URL未设置时脚本不会卡住(|| true那一行就是干这个的)
最后一个最容易被漏——脚本在生产环境因为一个没配的变量卡住,比不写脚本更糟。