
先给第一次来的读者:这篇解决什么问题
VPS(Virtual Private Server,虚拟专用服务器)就是你租在机房里的一台远程小电脑,按月或按年付费,上面跑着博客、代理服务和各种脚本。磁盘满是最常见的"慢性病":服务每天写日志,几个月下来不知不觉就把硬盘塞满,轻则新日志写不进去、服务报错,重则连系统更新和备份都失败。
最危险的清理方式是凭感觉删:rm -rf 一行敲下去,删错数据库或配置文件就只能靠备份抢救——前提是你有备份。这篇给你一个思路相反的脚本:默认只演练不真删,先把"最大的文件是哪些、哪些日志过期了"列清楚,你确认无误后加一个 --apply 参数才真正执行。全文代码只用 Python 标准库,不用装任何第三方包,我在本地真实跑通过,输出原样贴在下面。
前置知识:一台能 SSH 登录的 Linux 服务器、会用命令行、Python 3.10 以上。清理之前先读一遍备份篇:重要数据先有备份,再谈删除。
清理前先定三条规矩
- 只删日志类文件:脚本只认
.log、.out、.gz后缀,数据库、配置文件、网站数据一律不动。最大的文件不一定是能删的——下面实测里最大的就是一个非日志文件,脚本原样放过了它。 - 默认演练,显式确认才真删:不加
--apply时只打印清单;真正删除必须是你主动敲下这个参数。定时任务里也要先跑一周演练,确认清单合理再上--apply。 - 禁区目录直接拒绝:
/、/etc、/usr、/home、/root这类目录传进来,脚本直接返回错误,不执行任何删除。系统自带的日志目录(如/var/log)请用系统工具 logrotate 管理,别拿这个脚本去扫。
完整代码
把下面代码存为 disk_cleaner.py:
#!/usr/bin/env python3
"""磁盘清理脚本:列出大文件 + 删除过期日志,默认只演练不真删。
用法:python3 disk_cleaner.py <目录> [--days 30] [--apply]
只用标准库,Python 3.10+。
"""
import sys
from datetime import datetime, timedelta
from pathlib import Path
# ---------- 安全护栏 ----------
FORBIDDEN = {"/", "/etc", "/usr", "/bin", "/sbin", "/boot", "/home", "/root"}
LOG_SUFFIXES = {".log", ".out", ".gz"} # 只删这些后缀,数据库、配置文件一律不动
def human(n):
for u in ("B", "KB", "MB", "GB"):
if n < 1024:
return f"{n:.1f}{u}"
n /= 1024
return f"{n:.1f}TB"
def main():
if len(sys.argv) < 2:
print("用法:python3 disk_cleaner.py <目录> [--days 30] [--apply]")
return 2
target = Path(sys.argv[1]).resolve()
days = 30
apply = False
args = sys.argv[2:]
if "--days" in args:
days = int(args[args.index("--days") + 1])
if "--apply" in args:
apply = True
if str(target) in FORBIDDEN or not target.is_dir():
print(f"[拒绝] 不安全的目标目录:{target}")
return 2
cutoff = datetime.now() - timedelta(days=days)
files = [p for p in target.rglob("*") if p.is_file()]
files.sort(key=lambda p: p.stat().st_size, reverse=True)
print(f"扫描 {target}:共 {len(files)} 个文件")
print("最大的 5 个文件:")
for p in files[:5]:
print(f" {human(p.stat().st_size):>8} {p.relative_to(target)}")
expired = [p for p in files
if p.suffix in LOG_SUFFIXES
and datetime.fromtimestamp(p.stat().st_mtime) < cutoff]
freed = sum(p.stat().st_size for p in expired)
print(f"超过 {days} 天未动的日志类文件:{len(expired)} 个,"
f"共 {human(freed)}")
for p in expired:
print(f" {'删除' if apply else '将删除'} {p.relative_to(target)}")
if not apply:
print("[演练模式] 未删除任何文件,加 --apply 才真正执行")
return 0
for p in expired:
p.unlink()
print(f"[完成] 已删除 {len(expired)} 个文件,释放约 {human(freed)}")
return 0
if __name__ == "__main__":
sys.exit(main())
用到的标准库只有 pathlib(目录遍历)和 datetime(过期判断),Python 官方文档可查版本。
真实运行输出
测试目录里放了 3 个文件:40 天没动过的 old-app.log、2 天前更新的 new-app.log、以及一个 8KB 的非日志文件 big-data.bin。先跑演练模式:
扫描 /tmp/clean-test/logs:共 3 个文件
最大的 5 个文件:
7.8KB big-data.bin
4.9KB old-app.log
2.0KB new-app.log
超过 30 天未动的日志类文件:1 个,共 4.9KB
将删除 old-app.log
[演练模式] 未删除任何文件,加 --apply 才真正执行
注意两点:最大的 big-data.bin 因为不是日志后缀,没有进入删除清单;2 天前的 new-app.log 因为没超过 30 天,也没动。确认清单无误后加 --apply 再跑一遍,输出最后一行变成 [完成] 已删除 1 个文件,释放约 4.9KB,事后检查目录里只剩 big-data.bin 和 new-app.log——删且只删了该删的。
三步用起来
第 1 步:把脚本存到服务器,例如 /usr/local/bin/disk_cleaner.py,先对你应用的日志目录跑演练:
python3 /usr/local/bin/disk_cleaner.py /var/log/你的应用 --days 30
把路径换成你自己的应用日志目录,不要直接指向 /var/log 或备份目录。
第 2 步:看演练清单,重点看"最大的 5 个文件"那一节——如果榜首是数据库或上传文件,说明磁盘压力不在日志,删日志治标不治本,得去处理真正的大文件。
第 3 步:清单合理后,再挂定时任务,每周日凌晨 3 点跑一次真删:
0 3 * * 0 python3 /usr/local/bin/disk_cleaner.py /var/log/你的应用 --days 30 --apply >> /var/log/disk-cleaner.log 2>&1
两个常见坑
- 正在被写入的日志删了还会长回来:如果某个日志文件每天都在变大,说明对应的服务日志级别或轮转没配好,删只是缓刑。先给服务配好日志轮转,再用这个脚本兜底。
- 别把备份目录当清理目标:备份文件常带
.gz后缀,正好撞上脚本的删除规则。清理目录和备份目录必须分开,这是比脚本本身更重要的规矩。
和本系列其他文章的关系
这篇是"发现问题—解决问题"链条里的一环:先用巡检脚本发现磁盘告警,再用本篇清理过期日志,清理前按备份篇确认重要数据已有备份。三步走完,磁盘爆满这类问题基本就不会半夜找上门了。
