如何写一个文件版本管理脚本

wen 实用脚本 2

手把手教你写一个文件版本管理脚本(附完整代码)

目录导读

  1. 为什么你需要一个版本管理脚本? —— 从“最终版V3”说起
  2. 版本管理的核心逻辑 —— 你需要解决的3个问题
  3. 脚本设计思路 —— 从需求到功能拆解
  4. 实战:基于Bash与Python双版本的脚本实现(含详细注释)
  5. 进阶技巧:自动清理、日志审计与Git联动
  6. 常见问题Q&A —— 回答你踩过的5个坑
  7. —— 让版本管理成为你的习惯

为什么你需要一个版本管理脚本?

你一定遇到过这样的场景:桌面上的文件叫 项目方案_最终版.doc,一周后变成 项目方案_最终版2.doc,再后来是 项目方案_真正最终版_别改了.doc,这种手动改名的方式不仅混乱,而且极易丢失历史版本。

如何写一个文件版本管理脚本

一个版本管理脚本的核心价值在于:自动化、可追溯、可回滚,它能够在文件修改时自动保存快照,记录每次变更的内容和作者,并允许你一键恢复到任意历史版本,对于独立开发者、小型团队乃至个人文件管理,这比依赖Git(学习成本高)或云盘(不含版本差异对比)更轻量实用。


版本管理的核心逻辑

设计脚本前,你需要明确三个核心逻辑:

  • 触发机制:何时生成新版本?常见有3种——定时检查(每隔N分钟)、文件变更检测(MD5哈希变化)、手动执行(命令触发)。
  • 存储结构:版本文件放哪里?建议使用独立目录(如 ./versions/),并按 文件名_时间戳.扩展名 命名,便于排序和查找。
  • 保留策略:无限保存会占用大量空间,常见策略有:保留最近N个版本、按时间保留(如每天一个)、压缩旧版本。

脚本设计思路

1 需求定义

以“保护指定目录下的特定类型文件”为例:

  • 监视 ./docs/ 下的 .md.txt 文件。
  • 每次检测到文件内容变化(MD5改变),自动复制到 ./versions/ 目录。
  • 版本文件命名:原文件名_YYYYMMDD_HHMMSS.扩展名
  • 每个文件最多保留10个版本,超出后删除最老版本。

2 技术选型

  • Bash:适合Linux/macOS,轻量简单,但跨平台弱。
  • Python:跨平台、易读、支持复杂逻辑,推荐,本文提供双版本示例。

实战:脚本实现

1 Python版本(推荐)

import os
import hashlib
import shutil
from datetime import datetime
WATCH_DIR = "./docs"          # 监视目录
VERSION_DIR = "./versions"    # 版本存储目录
MAX_VERSIONS = 10             # 每个文件最大版本数
WATCH_EXT = [".md", ".txt"]   # 监视的文件类型
def get_md5(filepath):
    """计算文件的MD5值,用于检测变化"""
    hash_md5 = hashlib.md5()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()
def save_version(filepath):
    """保存当前文件为新版本"""
    if not os.path.exists(VERSION_DIR):
        os.makedirs(VERSION_DIR)
    filename = os.path.basename(filepath)
    base, ext = os.path.splitext(filename)
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    version_name = f"{base}_{timestamp}{ext}"
    target_path = os.path.join(VERSION_DIR, version_name)
    shutil.copy2(filepath, target_path)
    print(f"[备份] {filename} -> {version_name}")
    # 清理旧版本
    cleanup_versions(base, ext)
def cleanup_versions(base, ext):
    """只保留最新MAX_VERSIONS个版本"""
    pattern = f"{base}_*{ext}"
    import glob
    version_files = glob.glob(os.path.join(VERSION_DIR, pattern))
    version_files.sort(reverse=True)  # 按文件名降序(时间新在前)
    if len(version_files) > MAX_VERSIONS:
        for old_file in version_files[MAX_VERSIONS:]:
            os.remove(old_file)
            print(f"[清理] {os.path.basename(old_file)}")
def main():
    # 建立文件MD5缓存
    md5_cache = {}
    print("开始监视文件变化...(按Ctrl+C停止)")
    while True:
        for root, _, files in os.walk(WATCH_DIR):
            for file in files:
                if not any(file.endswith(ext) for ext in WATCH_EXT):
                    continue
                filepath = os.path.join(root, file)
                current_md5 = get_md5(filepath)
                # 如果MD5变化,保存版本
                if filepath in md5_cache:
                    if md5_cache[filepath] != current_md5:
                        save_version(filepath)
                else:
                    # 首次发现,也保存初始版本
                    save_version(filepath)
                md5_cache[filepath] = current_md5
        # 每5秒扫描一次
        import time
        time.sleep(5)
if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\n已停止版本管理脚本。")

代码说明

  • 使用MD5检测文件内容变化,比时间戳更可靠。
  • 版本文件按时间戳命名,天然排序。
  • 清理函数确保每个文件最多保留10个版本。

2 Bash版本(简易版)

#!/bin/bash
WATCH_DIR="./docs"
VERSION_DIR="./versions"
MAX_VERSIONS=10
EXTENSIONS=("md" "txt")
# 获取文件MD5
get_md5() {
    md5sum "$1" | awk '{print $1}'
}
# 保存版本
save_version() {
    local filepath="$1"
    local filename=$(basename "$filepath")
    local base="${filename%.*}"
    local ext="${filename##*.}"
    local timestamp=$(date +%Y%m%d_%H%M%S)
    local version_file="${VERSION_DIR}/${base}_${timestamp}.${ext}"
    cp "$filepath" "$version_file"
    echo "[备份] $filename -> $(basename "$version_file")"
    # 清理旧版本
    local count=$(ls -1 "${VERSION_DIR}/${base}_"*".${ext}" 2>/dev/null | wc -l)
    while [ $count -gt $MAX_VERSIONS ]; do
        local oldest=$(ls -1tr "${VERSION_DIR}/${base}_"*".${ext}" | head -1)
        rm "$oldest"
        echo "[清理] $(basename "$oldest")"
        count=$((count-1))
    done
}
# 主循环
mkdir -p "$VERSION_DIR"
declare -A md5_map
echo "开始监视(Ctrl+C停止)..."
while true; do
    for file in $(find "$WATCH_DIR" -type f \( -name "*.md" -o -name "*.txt" \)); do
        current_md5=$(get_md5 "$file")
        old_md5="${md5_map["$file"]}"
        if [ -z "$old_md5" ]; then
            save_version "$file"
        elif [ "$old_md5" != "$current_md5" ]; then
            save_version "$file"
        fi
        md5_map["$file"]="$current_md5"
    done
    sleep 5
done

进阶技巧

1 日志审计

在脚本中添加一个audit.log,记录每次备份/清理操作的时间、文件名、MD5值,这在追踪“谁改坏了文件”时至关重要。

2 与Git的联动

如果你同时使用Git,可以考虑:当脚本检测到新版本时,自动执行git addgit commit,这样,每个文件版本都对应一个Git提交,利用Git的diff功能可以查看细微变化。

3 增量压缩存储

将超过3天的旧版本用gzip压缩,节省90%空间,恢复时先解压再访问。


常见问题Q&A

Q1:脚本一直循环检测,会不会太耗CPU?
答:不会,MD5计算是IO密集型,5秒间隔的CPU占用几乎忽略不计,如果文件特别大,可将间隔调至10秒。

Q2:我只想备份某个特定文件,而不是整个目录?
答:修改WATCH_DIR为单个文件路径,然后在循环中移除os.walk,直接对该文件检测即可。

Q3:误操作删了很多版本,还能恢复吗?
答:如果删除了versions目录,无法恢复,建议定期将versions目录同步到云盘或外部硬盘。

Q4:脚本能否作用于Windows系统?
答:Python版本完全跨平台,Bash版本需通过Git Bash或WSL在Windows下运行。

Q5:如何将脚本设置为开机自启?
答:Linux使用crontab@reboot;macOS使用launchd;Windows使用“任务计划程序”。


一个文件版本管理脚本,本质上是把“手动复制改名”的重复劳动自动化,通过本文的Python或Bash实现,你可以获得:

  • 自动保存每个修改瞬间的快照。
  • 一键回到任意历史版本。
  • 无需学习Git即可享受版本控制的核心便利。

建议从Python版本入手,根据自身需求调整WATCH_DIRMAX_VERSIONS,未来可以扩展为图形界面(Tkinter)或Web服务(Flask)。管理版本不是为了保存废案,而是为了在泥潭中有一条清晰的安全绳,运行你的脚本,再也不用改文件名加“最终版”了。

抱歉,评论功能暂时关闭!