跨平台的磁盘空间自动维护脚本
问题背景
系统的日志、缓存,或程序的输出、结果等文件写入频率很高,随着时间的推移,磁盘空间的占用慢慢增长,这些超过一定时限的数据,比如 3 个月前的数据,可能没有太大的价值(在此假设重要的事件已经收集并上报),那么在系统空间不足时(如剩余 10% 的可用空间),可删除这些文件,以确保系统和服务的正常运转。
Linux 解决方案
特性
- 支持清理多个文件夹
- 删除过期文件的空间,配合
cron
可清理自动删除 - 触发清理的阈值可调,阈值与已占用空间的百分比进行比较
- 支持空跑模式,空跑时不实际删除文件,而是将待删除的文件打印出来进行调试
- 可自定义时间周期,比如依次删除 3 个月,2 个月,1 个月,两周,一周前的数据
用法
直接调用
# 先调试,打印待删除文件
DEBUG=1 /usr/local/bin/remove_old_files.sh 80
# 实际删除
/usr/local/bin/remove_old_files.sh 80
定期调用
比如每天凌晨 3 点,自动对目录进行清理。
# 进入 cron 定期任务编辑模式
crontab -e
# m h dom mon dow command
0 3 * * * /usr/local/bin/remove_old_files.sh 80
Bash 代码
#!/bin/bash
# Target directories
# 目标目录,比如高频输出结果的文件夹,临时目录,缓存目录等
DIRS=("/path/to/directory1" "/path/to/directory2" "/path/to/directory3")
# Usage space threshold (in percent)
# 等到占用空间高于多少百分比时执行清理操作,默认 90,即剩余 10% 空间时触发清理
USAGE_PERCENT=${1:-90}
# Debug flag
# 是否仅打印文件名,而不实际删除,即 dry-run 空跑
DEBUG=${DEBUG:-0}
# Days old that files must be to get removed
# 时间范围依次递减,若更旧的文件删除后,复查已用空间百分比已满足要求,就不再删除时间较新的文件
MTIMES=(90 60 30 15 7)
# Function to check disk usage
# 检查已用空间是否超标
check_disk_usage() {
local DIR=$1
# Return true if the disk usage is greater than or equal to the passed value
# 调用 df 命令,略过第一行,输出第 5 列 `Use%`,并去掉末尾的百分号,再跟设定的已用空间百分比阈值比较;
# 已用空间大于阈值时,返回真,表示需要清理;否则,返回假。
if [ $(df $DIR | awk 'NR==2 {print $5}' | sed 's/%//g') -ge $USAGE_PERCENT ]; then
return 0
else
return 1
fi
}
# Function to find and remove files older than a certain number of days
# 根据 mtime 范围来查找并删除文件,比如 +30 表示 30 天前的文件,而 -30 表示 30 内的文件。
# `-mtime` 参数解释:
# -mtime n
# File was last modified n*24 hours ago. When find figures out how many 24-hour periods ago the file was last modification, any fractional part is ignored, so to match -atime +1, a file has to have been accessed at least two days ago.(换句话,今天已经流逝的时间不算在内(不能被 24 整数,所以忽略掉了),然后再向前数 N 个 24 小时前)
remove_old_files() {
local DIR=$1
local MTIME=$2
if [ "$DEBUG" -eq 1 ]; then
find $DIR -type f -mtime +$MTIME -print
else
find $DIR -type f -mtime +$MTIME -exec rm -f {} \;
fi
}
# Iterate over the directories and time periods and try to remove old files
# 外遍历目标目录,再内遍历时间范围,组合两层循环的参数,调用删除旧文件的函数
for DIR in ${DIRS[@]}; do
for MTIME in ${MTIMES[@]}; do
if check_disk_usage $DIR; then
remove_old_files $DIR $MTIME
fi
done
# Check one final time if the disk space usage is still greater than or equal to the specified percent
# 经过了上面的删除后,最后进行一次复查,如果已使用空间仍然大于阈值,则输出日志
if check_disk_usage $DIR; then
# If so, output a message that will be mailed to the user
# 当此脚本被 crontab 定期执行时,任何输出都会发送到用户的邮箱
echo "Warning: Disk space usage in $DIR is still greater than or equal to $USAGE_PERCENT% after deleting old files."
fi
done
Windows 解决方案
考虑到 .bat
支持的语言特性较少, 缺少 array
, Pipe
语法和 sed
, df
这些常用命令,因此改用 Python 实现。
特性
和 Bash
版本基本一致,只不过定期执行需要配合 Windows 的 taskschd.msc
计划任务程序。
用法
直接调用
REM 输出待删除文件
python remove_old_files.py -p 80 -d
REM 实际删除
python remove_old_files.py -p 80
定期调用
配合 taskschd
计划任务,设定重复执行的周期。
Python 代码
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# author: Muwaii
# datetime: 6/6/2023 5:20 PM
# software: PyCharm
import argparse
import os
import pathlib
import shutil
import time
def parse_cmd_args():
# Parsing arguments for custom percent and debug mode
parser = argparse.ArgumentParser()
parser.add_argument("target_directories", type=pathlib.Path, nargs="+", help="targets")
parser.add_argument("-m", "--mtimes", type=int, nargs="+", default=[90, 60, 30],
help="Disk usage percent to trigger file removal")
parser.add_argument("-p", "--percent", type=int, default=90, help="Disk usage percent to trigger file removal")
parser.add_argument("-d", "--debug", action="store_true", help="Enable debug mode (does not actually delete files)")
args = parser.parse_args()
return args
def check_disk_usage(_dir, usage_percent):
total, used, free = shutil.disk_usage(_dir)
percent_used = used / total * 100
return percent_used >= usage_percent
def remove_old_files(_dir, days_old, is_debug=False):
cutoff_time = time.time() - days_old * 24 * 60 * 60
for root, dirs, files in os.walk(_dir):
for file in files:
file_path = os.path.join(root, file)
if os.path.getmtime(file_path) < cutoff_time:
if is_debug:
print(f"\tWould remove: {file_path}")
else:
print(f"\tReal remove: {file_path}")
os.remove(file_path)
def main():
args = parse_cmd_args()
# Target directories
dirs = args.target_directories
# Days old that files must be to get removed
mtimes = args.mtimes
for _dir in dirs: # type: pathlib.Path
if not _dir.exists():
print(f"dir `_dir` not found, skip.")
continue
for mtime in mtimes:
print(f"check mtime {mtime} with dir {_dir}...")
if check_disk_usage(_dir, args.percent):
remove_old_files(_dir, mtime, is_debug=args.debug)
# Check one final time if the disk space usage is still greater than or equal to the specified percent
if check_disk_usage(_dir, args.percent):
print(f"Warning: Disk space usage in {_dir} is still greater than or "
"equal to {args.percent}% after deleting old files.")
if __name__ == '__main__':
main()
小结
本文针对临时文件过多导致磁盘空间不足的场景,提出了两个常用平台的解决方案,Linux 使用原生的 Bash 实现,而 Windows 受限于 bat
批处理语法过于简陋,改用 Python
实现,整体上两者实现的特性和用法差异不大,都能够解决开头提到的问题。