干活久了你会发现,运维工作里最不起眼的“删日志”反而是翻车率最高的操作之一。练气期新手容易莽,上来就rm -rf /logs/*,等到服务起不来、查不了历史故障、甚至误删了数据库binlog,才知道疼。这篇不是讲find命令的用法手册,而是把一套能直接上生产环境的日志删除实操脚本掰开揉碎,从需求边界、命令选型到坑点复盘,完整走一遍。适合刚接手服务器日常巡检、准备写第一个定时任务的新手运维,也适合那些已经写了脚本但半夜经常被告警吵醒的老哥对照自查。
1. 为什么99%的新运维都会在日志清理上栽跟头
1.1 日志堆积不是“磁盘满了”这一个问题
很多新手对日志清理的理解就是“磁盘不够了,删点东西”。但日志堆积的隐患远不止磁盘告警那么简单。
先看最直接的问题:磁盘写满。Linux系统分区分区没有预留空间,日志把inode用完,连touch一个空文件都报No space left on device。更阴的是,有些应用服务崩溃后不停写错误日志,每秒钟几个GB地涨,等你登录服务器想排查时,df -h已经100%,vi都打不开,只能靠dd盲清,非常被动。
第二个问题是日志轮转失效。很多Java应用使用Log4j2或Logback自带的滚动策略,但滚动策略只负责“切分文件”,不负责“删除旧文件”。要是配置里只写了maxHistory没生效,或者应用长期运行未重启,catalina.out这种不会被轮转的日志就会无限膨胀。到后来一个文件几十GB,不仅占磁盘,还会拖慢应用本身的日志写入速度,IO都被它吃掉了。
第三个问题很容易忽略:审计和排障需要“留有痕迹”。日志全删了,等出了线上故障要回溯时间线时,发现关键的中间几天的日志早就被清掉了,只能靠回忆和猜。日志删除的边界不是“能删的都删掉”,而是“保留足够长的回溯周期,同时不让磁盘持续增长”。
1.2 手动清理为什么一定不可持续
练气期新人最常见的操作是接到磁盘告警,然后手动执行:
bash复制rm -f /home/app/logs/*.log
这条命令有四个问题:
- 不区分文件时间,会把所有
.log全部清掉,包括昨天刚出的故障日志,想复盘就没了。 - 不看错误日志。
rm虽然报No such file or directory,但即使只漏掉一个被进程占用的日志文件,磁盘空间也得不到真正回收。 - 没有保留周期,全凭感觉。今天觉得日志没用了就全删,明天觉得心虚又不敢删。
- 无法应对高峰期。地震级的告警集中在凌晨两三点,你可能根本来不及手动清。
所以真正能长期运转的日志清理方案一定是脚本化+定时化的。脚本负责“严格按规则识别文件”,定时任务负责“到点自动执行”,人只需要偶尔看一眼报告的清理结果,这才是运维该有的状态。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从需求到落地:脚本设计的分层拆解
2.1 先界定边界:哪些日志能删,哪些不能动
写脚本之前,第一件事不是写代码,而是搞清楚“删除对象的边界”。否则脚本越强大,造成的破坏越不可挽回。我给自己定了几条铁律:
- 只处理明确指明的日志目录,比如
/var/log、/home/app/logs、/data/logs,绝不允许用“匹配全盘”的写法。 - 只按文件名后缀过滤,比如
*.log、*.out、*.gz,对没有后缀的目录或文件,宁可保留也不动。 - 必须按修改时间判断,保留最近N天,N天以前的才允许删除。
- 如果目录里混有正在写入的活动日志,它们的
mtime通常是当前时间,按天切割后,几天前的文件自然会被过滤掉,不会删到正在用的文件。
换句话说,脚本的逻辑非常简单:在指定目录下,找出“后缀是.log或匹配正则”的普通文件,并且“最后修改时间早于N天”,然后删除。
2.2 脚本骨架与分层设计思路
把这份脚本拆开看,其实可以分成四个层次:
text复制输入层:目录路径、保留天数、文件名通配符
规则层:按时间、按类型、按路径双重过滤
执行层:删除命令、dry-run开关、日志记录
报告层:统计删除数量、释放空间、输出清理结果
这样的分层有实际意义:新运维拿到脚本后,不需要看懂每行bash,只需改开头的几个变量就能安全使用;而需要扩展时,也能快速定位到对应段落。我见过很多脚本把所有逻辑挤在一行里,看着很酷,但出了问题想排查非常痛苦。
下面给出一个可以直接剪贴使用的基础版本:
bash复制#!/bin/bash
# description: 日志清理脚本,按保留天数删除N天前的日志文件
# author: laolin
# ---------- 输入配置 ----------
LOG_DIRS=(
"/home/app/logs"
"/data/logs"
"/var/log/myapp"
)
RESERVE_DAYS=30
FILE_PATTERN="*.log"
# ---------- 其他配置 ----------
DRY_RUN=false
LOG_FILE="/var/log/cleanup_script.log"
# ---------- 工具函数 ----------
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> "$LOG_FILE"
}
# ---------- 主逻辑 ----------
for dir in "${LOG_DIRS[@]}"; do
if [ ! -d "$dir" ]; then
log_message "[WARN] 目录不存在,跳过: $dir"
continue
fi
if [ "$DRY_RUN" = true ]; then
log_message "[DRY-RUN] 以下文件将被删除(当前仅模拟): $dir"
