
服务器或个人电脑上经常会有一类目录:每天生成备份、日志、导出包或压缩文件。刚开始文件不多,看起来不用管;时间一长,旧文件越来越多,磁盘空间就会被慢慢吃掉。
这次整理备份目录时,我采用了一个很实用的办法:用脚本扫描目标目录,按文件名时间排序,只保留最近 30 个文件,超过的旧文件自动删除。这个方法不依赖复杂工具,适合处理命名规则稳定的备份文件。
适用场景
这个方法适合下面这类文件:
- 文件名里包含日期和时间;
- 文件名按字母排序时,顺序也等于时间顺序;
- 只需要保留最近若干份;
- 旧文件可以直接删除,不需要归档到其他位置。
例如下面这种命名方式就很适合:
20260701-023000.tar.gz 20260702-023000.tar.gz 20260703-023000.tar.gz
因为日期在前、时间在后,所以直接按文件名排序,就能得到从旧到新的顺序。
核心思路
整个逻辑可以拆成六步:
- 指定要清理的目录;
- 指定要保留的文件数量,例如 30;
- 只筛选符合命名规则的文件;
- 把文件按名称排序;
- 计算需要删除多少个旧文件;
- 循环删除排序列表最前面的旧文件。
这里最重要的是第三步:不要简单地删除目录里的所有旧文件,而是先限定文件名规则。这样可以避免误删临时说明、手动导出的文件或其他不属于自动备份的内容。
一个 Bash 示例
下面是一个简化后的脚本示例。它会在指定目录中寻找符合规则的备份文件,只保留最新 30 个。
#!/bin/bash
set -u
BACKUP_DIR="/path/to/backups"
KEEP_COUNT=30
DRY_RUN="${DRY_RUN:-0}"
files=()
while IFS= read -r path; do
name="$(basename "$path")"
if [[ "$name" =~ ^[0-9]{8}-[0-9]{6}\.tar\.gz$ ]]; then
files+=("$path")
fi
done < <(find "$BACKUP_DIR" -maxdepth 1 -type f -name '*.tar.gz' -print)
if [ "${#files[@]}" -le "$KEEP_COUNT" ]; then
echo "${#files[@]} files found; nothing to delete."
exit 0
fi
sorted_files=()
while IFS= read -r line; do
sorted_files+=("$line")
done < <(printf '%s\n' "${files[@]}" | sort)
delete_count=$((${#sorted_files[@]} - KEEP_COUNT))
echo "${#sorted_files[@]} files found; deleting oldest $delete_count."
for ((i = 0; i < delete_count; i++)); do
file="${sorted_files[$i]}"
if [ "$DRY_RUN" = "1" ]; then
echo "DRY_RUN: would delete $file"
else
rm -f "$file"
echo "DELETE: $file"
fi
done
实际使用时,只需要把 BACKUP_DIR 改成自己的备份目录,并根据文件后缀调整匹配规则。
为什么要加 DRY_RUN
删除脚本最怕的不是写得复杂,而是第一次运行就删错文件。所以我建议保留一个 DRY_RUN 开关。
预览模式下,脚本只打印“将会删除哪些文件”,不真正执行删除。确认列表正确以后,再关闭 DRY_RUN 正式清理。
DRY_RUN=1 ./cleanup-old-files.sh
这一步看起来多余,但对任何会批量删除文件的脚本来说都很值得。尤其是目录里文件多、文件名相似时,先预览可以避免很多低级事故。
为什么按文件名排序,而不是按修改时间
很多人会直接按文件修改时间删除旧文件,这当然也可以。但如果备份文件名本身已经带有生成时间,我更倾向于按文件名排序。
原因有两个:
- 文件复制、迁移、恢复时,修改时间可能会变化;
- 文件名里的时间通常更能代表备份实际生成时间。
前提是命名格式要稳定,最好使用“年月日-时分秒”这种从大到小排列的格式。不要使用“月日年”或中文日期混排,否则普通排序可能不等于时间排序。
加上日志会更好维护
如果这个脚本只是偶尔手动运行,直接在屏幕上输出结果就够了。但如果要放到后台定时执行,最好把每次运行结果写入日志。
日志至少应该记录这些信息:
- 本次发现了多少个匹配文件;
- 保留数量是多少;
- 需要删除多少个旧文件;
- 实际删除了哪些文件;
- 如果出错,错误原因是什么。
这样以后发现磁盘空间异常、备份数量不对或清理任务没有执行时,可以直接翻日志,而不是凭感觉猜。
几个容易忽略的细节
第一,保留数量要检查是否合法。比如 KEEP_COUNT 应该是非负整数,不能是空值、中文或其他字符串。
第二,目标目录不存在时不要直接报一堆错误。更好的做法是记录“目录不可用”,然后安全退出。外接硬盘没挂载、网络盘暂时不可用时,这种情况很常见。
第三,查找文件时要限制目录层级。只清理当前目录就用 maxdepth 1,避免误扫到子目录里的其他文件。
第四,删除前只处理白名单格式。也就是说,不是“除了最新 30 个以外都删”,而是“符合备份命名规则的文件里,只保留最新 30 个”。这两个说法差别很大。
适合定时任务自动执行
这个脚本很适合放进系统定时任务里执行,比如每天凌晨跑一次。备份文件通常也是按天生成,清理脚本每天执行一次就足够了。
不过定时任务上线前,建议按这个顺序验证:
- 先手动运行 DRY_RUN,确认会删除哪些文件;
- 再手动正式运行一次,确认删除结果正确;
- 最后交给系统定时任务执行,并检查日志是否正常写入。
小结
循环删除旧文件、只保留最近 30 个文件,本质上是一个简单但很可靠的维护策略。关键不在于脚本有多复杂,而在于规则要清楚:只处理指定目录、只匹配指定命名格式、按时间顺序排序、先预览再删除、执行结果要有日志。
对于备份、日志和定期导出文件来说,这种保留策略可以避免磁盘被旧文件慢慢占满,也能让目录长期保持可控。比起等空间报警后再手动清理,提前写好自动保留规则要稳妥得多。