编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • ScriptHub 脚本工具箱
  • Python

  • Shell-Bash

  • 工具脚本

    • 工具脚本速查
    • 哈希校验
    • Base64编码
    • AES加解密
    • RSA签名验签
    • JWT令牌
    • JSON与YAML
    • XML与CSV
    • 编码转义
    • 图片转换
    • 文档转换
    • 批量重命名
    • 分割合并
    • 目录同步
      • 同步的三种粒度
      • 一、filecmp——目录差异分析
      • 二、shutil——双向同步
      • 三、rsync 封装
      • 四、Shell 命令
    • 文件监控
    • 压缩归档
    • 文件去重
    • cURL速查
    • HTTP调试
    • 端口DNS
    • 抓包代理
  • ScriptHub
  • 工具脚本
杨充
2021-08-19
目录

目录同步

# 目录同步

filecmp 比较目录差异、增量/全量同步、rsync 封装、排除模式。

# 同步的三种粒度

目录同步是一个经典的"比较—决策—执行"问题。根据比较粒度不同,有三种策略:

策略 比较方式 适用场景 准确性
时间戳 st_mtime 文件数量少、无跨时区 ⚠️ 时钟偏差会导致误判
大小+时间戳 st_size + st_mtime 一般文件同步 较好,但内容相同时间不同的文件会重传
内容哈希 MD5/SHA256 需要精确保证一致性 最准确,但计算成本高

stat 结构体字段:每次调用 os.stat() 返回文件的元数据。关键字段:st_size(字节数)、st_mtime(最后修改时间——Unix 时间戳浮点数)、st_ctime(元数据变更时间/Windows 创建时间)。注意 st_mtime 的精度在 ext4 上是纳秒级,但 Python os.path.getmtime 返回浮点秒——比较时应避免 ==,用 <= 容忍精度误差。

# 一、filecmp——目录差异分析

filecmp.dircmp 内部使用两级过滤:先比 os.stat() 元数据(类型+大小+时间),对同名且同大小的文件才计算 hashlib 浅哈希(shallow=True)进行比较。注意这不同于文件去重的"两级过滤"——这里的目的是找出差异,而非发现重复。

#!/usr/bin/env python3
import filecmp, os

def diff_dirs(dir1, dir2):
    """比较两个目录——filecmp 底层用 stat 做快速过滤"""
    cmp = filecmp.dircmp(dir1, dir2)

    for f in cmp.left_only:
        print(f"  只在左: {f}")
    for f in cmp.right_only:
        print(f"  只在右: {f}")
    for f in cmp.diff_files:
        print(f"  内容不同: {f}")
    print(f"\n  相同文件: {len(cmp.same_files)} 个")

    for sub in cmp.subdirs:
        print(f"\n--- 子目录: {sub} ---")
        diff_dirs(os.path.join(dir1, sub), os.path.join(dir2, sub))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 二、shutil——双向同步

增量同步的原理:对源目录中每个文件,检查目标目录中是否存在同名文件且大小相同 + 修改时间不更新。满足条件则跳过(未变化),否则复制。这是一个启发式算法——无法检测文件内容被"还原"为旧版本但时间戳更新的情况,但覆盖了 99% 的实际场景。

shutil.copy2 相比 shutil.copy 额外保留 st_mode(权限)和 st_mtime(修改时间)——这对增量同步很关键:如果复制后时间戳变了,下次同步时会误判为"需要更新"。

#!/usr/bin/env python3
import shutil, os
from pathlib import Path

def sync_dirs(src, dst, delete_extra=False):
    """增量同步——只复制大小/时间不同的文件"""
    src, dst = Path(src), Path(dst)
    copied, skipped = 0, 0

    for s in src.rglob('*'):
        rel = s.relative_to(src)
        d = dst / rel
        if s.is_dir():
            d.mkdir(parents=True, exist_ok=True)
        else:
            if d.exists():
                ss, ds = s.stat(), d.stat()
                # 大小相同且修改时间不更新 → 跳过
                if ss.st_size == ds.st_size and ss.st_mtime <= ds.st_mtime:
                    skipped += 1; continue
            d.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(s, d)     # copy2 保留 mtime + permissions
            copied += 1

    # 镜像模式:删除目标中多余的文件(先处理新增,后删除——减少风险)
    if delete_extra:
        for d in dst.rglob('*'):
            if not (src / d.relative_to(dst)).exists():
                if d.is_file(): d.unlink()
                elif d.is_dir(): shutil.rmtree(d)
    print(f"✅ 复制 {copied} 个,跳过 {skipped} 个")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 三、rsync 封装

rsync 为什么快? 它的核心是 rolling checksum(滚动校验和) 算法:不直接比较两个完整文件的 MD5,而是将文件分成固定大小的块,逐块计算弱校验和(adler-32)+ 强校验和(MD5),只传输块哈希不匹配的部分。这意味着修改一个大文件中的一行——只传那个块,不重传整文件。这是 rsync 与其他同步工具的本质区别。

src.rstrip('/') + '/' 的含义:源路径末尾的 / 告诉 rsync 同步的是"内容"而非"目录本身"。没有 / 会在目标创建 src 子目录。

#!/usr/bin/env python3
import subprocess

def rsync_sync(src, dst, exclude=None, dry_run=False):
    cmd = ['rsync', '-avz', '--delete']
    if dry_run: cmd.append('--dry-run')
    if exclude:
        for pattern in exclude:
            cmd.extend(['--exclude', pattern])
    cmd.append(f"{src.rstrip('/')}/"); cmd.append(dst)

    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode == 0:
        print("✅ 同步完成")
    else:
        print(f"❌ 失败: {result.stderr}")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 四、Shell 命令

#!/bin/bash

rsync -avz --delete /src/ /dst/             # 增量镜像(最常用)
rsync -avzn /src/ /dst/                     # -n dry-run 预览变更
rsync -avz --ignore-existing /src/ /dst/    # 只复制新文件(不更新已有)
1
2
3
4
5
#工具#文件
上次更新: 2026/06/17, 12:47:39
分割合并
文件监控

← 分割合并 文件监控→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式