rsync源码解析 (5) 文件过滤规则系统

在上一章 文件列表 (File List) 中,我们了解到 rsync 如何通过创建一份详尽的"文件清单"来开启同步任务。我们知道了 rsync 在动手之前,会先列出所有待处理的文件和目录。但是,如果这份清单上有些东西我们并不想同步,该怎么办呢?

这就是本章的主角------文件过滤规则系统------大显身手的地方。它是 rsync 强大灵活性和控制力的核心体现,允许我们像一名经验丰富的行李打包专家一样,精确地告诉 rsync 哪些东西要带上,哪些要留在原地。

行李打包的艺术

想象一下,你正准备去热带岛屿度假,你的衣柜里有各种各样的衣服。你不会把所有衣服都塞进行李箱,而是会制定一套规则:

  • 规则 1:带上所有的 T 恤。
  • 规则 2:不带任何羊毛衫。
  • 规则 3:不带任何长裤。
  • 规则 4:对了,那件蓝色的沙滩短裤一定要带。
  • 规则 5:其他的衣服,就都不带了。

Rsync 的过滤系统就如同这份打包清单。你可以设定一系列的"包含"(include)和"排除"(exclude)规则。当 rsync 拿到完整的文件列表后,它会像一个听话的助手,拿着你的规则清单,逐一检查列表上的每个文件,然后做出决定:打包(同步)还是不打包(跳过)。

这个系统最关键、也是最容易让初学者困惑的一点是:第一个匹配到的规则说了算! 一旦 rsync 为某个文件找到了匹配的规则,它就会立刻做出决定,不再理会后面的任何规则。

核心用例:只同步源代码

让我们来看一个非常常见的场景。假设你有一个项目目录 my_project,结构如下:

css 复制代码
my_project/
├── src/
│   ├── main.c
│   └── utils.c
├── obj/
│   ├── main.o
│   └── utils.o
└── .gitignore

你想把这个项目备份到另一台服务器,但你只想备份重要的源文件(.c 文件)和配置文件(.gitignore),而不想备份编译过程中产生的临时文件(.o 文件)和 obj 目录。

使用 rsync 的过滤规则,我们可以轻松实现这个目标。一个经典的命令如下:

bash 复制代码
rsync -av \
  --include='*/' \
  --include='*.c' \
  --include='.gitignore' \
  --exclude='*' \
  my_project/ user@remote:/backup/

这个命令看起来有点复杂,但它完美地诠释了过滤规则的精髓。让我们把它翻译成"行李打包"的语言:

  1. --include='*/':"为了能进入子目录,先把所有目录都打包。" (这是让 rsync 能够递归进入 src 等目录的关键)
  2. --include='*.c':"所有以 .c 结尾的文件,打包。"
  3. --include='.gitignore':".gitignore 这个文件,打包。"
  4. --exclude='*':"对于剩下的所有其他东西,通通不打包。"

Rsync 会严格按照这个顺序来检查文件列表中的每一项。例如:

  • 遇到 src/ 目录:匹配规则 1 (*/),包含
  • 遇到 src/main.c 文件:不匹配规则 1,继续;匹配规则 2 (*.c),包含
  • 遇到 obj/main.o 文件:不匹配规则 1、2、3,继续;匹配规则 4 (*),排除

最终,只有你想要的文件被精确地挑选出来进行同步。

规则的内部运作:深入 exclude.c

那么,这些规则在 rsync 内部是如何存储和应用的呢?所有魔法都发生在 exclude.c 文件中。

规则的表示:struct filter_rule

每一条用户提供的过滤规则(无论是 --include 还是 --exclude)都会被解析并存储在一个名为 filter_rule 的结构体中。这些结构体像串珠一样,通过 next 指针连接成一个链表(filter_list),也就是我们的"规则清单"。

c 复制代码
// 文件: rsync.h (简化版)

struct filter_rule {
	struct filter_rule *next; // 指向链表中的下一个规则

	const char *pattern;      // 匹配模式,例如 "*.tmp" 或 "obj/"

	uint32_t rflags;          // 规则的标志位
	// ...
};

// rflags 的一些重要标志位:
#define FILTRULE_INCLUDE   0x0001 // 这是一个包含规则 (+)
#define FILTRULE_DIRECTORY 0x0020 // 这个规则只匹配目录
#define FILTRULE_WILD      0x0100 // 模式中包含通配符
  • pattern 存储着规则的具体模式。
  • rflags 是一个整数,通过不同的位(bit)来记录规则的属性。比如,FILTRULE_INCLUDE 位被设置,就表示这是一个包含规则,否则就是排除规则。

添加规则:add_rule()

当 rsync 解析命令行参数时,每遇到一个 --include--exclude,它就会调用 add_rule() 函数。这个函数负责:

  1. 创建一个新的 filter_rule 结构体。
  2. 解析规则字符串(比如 + *.c- *.o),填充 patternrflags 字段。
  3. 将这个新的规则追加到 filter_list 链表的末尾。
c 复制代码
// 文件: exclude.c (简化逻辑)

static void add_rule(filter_rule_list *listp, const char *pat, ..., filter_rule *rule, ...)
{
    // ... 解析 pat (模式), 比如识别末尾的 '/' 来设置 FILTRULE_DIRECTORY ...
    
    // 如果模式中含有通配符, 设置 FILTRULE_WILD 标志
    if (strpbrk(rule->pattern, "*[?")) {
        rule->rflags |= FILTRULE_WILD;
    }

    // 将新规则添加到链表的尾部
    if (!listp->tail) {
        listp->head = listp->tail = rule;
    } else {
        listp->tail->next = rule;
        listp->tail = rule;
    }
}

通过这个过程,命令行的所有过滤选项就被转换成了一个有序的、程序可以理解的规则链表。

应用规则:check_filter()

这是过滤系统的核心。在文件列表构建完毕后,rsync 的生成器 (Generator)进程会遍历列表中的每一个文件。对于每个文件,它都会调用 check_filter() 函数来决定该文件的"命运"。

check_filter() 的逻辑非常直白,完美地体现了"第一个匹配规则说了算"的原则。

c 复制代码
// 文件: exclude.c (简化逻辑)

int check_filter(filter_rule_list *listp, ..., const char *name, ...)
{
	filter_rule *ent;

	// 从头到尾遍历规则链表
	for (ent = listp->head; ent; ent = ent->next) {
		// 调用 rule_matches() 来检查当前文件名是否匹配当前规则的模式
		if (rule_matches(name, ent, ...)) {
			// 一旦匹配成功!
			report_filter_result(...); // 打印日志 (如果开了-v)

			// 如果是包含规则, 返回 1 (包含)
			// 如果是排除规则, 返回 -1 (排除)
			// 然后立刻退出函数!
			return ent->rflags & FILTRULE_INCLUDE ? 1 : -1;
		}
	}

	// 如果遍历完所有规则都没有找到匹配项
	return 0; // 返回 0 (未匹配,默认行为是包含)
}

这个函数的执行流程,我们可以用一个时序图来更清晰地展示:

sequenceDiagram participant Generator as 生成器 participant FileList as 文件列表 participant FilterRules as 过滤规则链表 Note over Generator, FilterRules: Rsync 启动,根据命令行参数构建过滤规则链表 Generator->>FileList: 构建源目录的完整文件列表 loop 遍历文件列表 Generator->>FileList: 获取下一个文件,比如 "src/main.o" Generator->>FilterRules: check_filter("src/main.o") Note right of FilterRules: 开始从头检查规则 FilterRules->>FilterRules: 检查规则1: `include '*/'` -> 不匹配 FilterRules->>FilterRules: 检查规则2: `include '*.c'` -> 不匹配 FilterRules->>FilterRules: 检查规则3: `include '.gitignore'` -> 不匹配 FilterRules->>FilterRules: 检查规则4: `exclude '*'` -> 匹配! Note right of FilterRules: 找到第一个匹配,立即决定并返回 FilterRules-->>Generator: 返回 "排除" (-1) Generator->>Generator: 将 "src/main.o" 从同步任务中划掉 end

rule_matches() 函数内部会调用 wildmatch.c 中的函数来处理复杂的通配符匹配,比如 *(匹配任意非空字符序列,但不包括 /)和 **(匹配任意字符序列,包括 /)。

更多规则来源

除了直接在命令行中使用 --include--exclude,rsync 还提供了更灵活的方式来管理过滤规则,这使得它在复杂场景下依然游刃有余。

  • 从文件中读取规则
    • --include-from=FILE--exclude-from=FILE:你可以把大量的规则写在一个文件里,每行一条,然后让 rsync 读取这个文件。这在规则非常多时特别有用。
    • .rsync-filter 文件(或通过 --filter 选项指定的文件):Rsync 可以在遍历目录时,自动查找并应用每个目录下的规则文件。这允许你为不同的子目录定义不同的同步策略,非常强大。

这些来自不同来源的规则,会被 rsync 智能地合并成一个总的规则链表,然后统一应用。例如,来自守护进程配置的规则会最先生效,然后是命令行 --filter 规则,最后是每个目录下的 .rsync-filter 规则。

总结

在本章中,我们揭开了 rsync 强大的过滤系统的面纱。我们学习到:

  • 核心思想:过滤系统就像一个行李打包清单,通过一系列"包含"和"排除"规则,精确控制哪些文件被同步。
  • 关键原则:"第一个匹配规则说了算"。一旦一个文件匹配了列表中的某条规则,它的命运就被决定了,后续规则将被忽略。
  • 内部实现
    • 所有规则都被解析成 struct filter_rule 对象,并链接成一个有序的 filter_list
    • check_filter() 函数负责遍历这个规则链表,为文件列表中的每个文件查找匹配的规则。
    • 通配符(如 *, ?, **)使得规则可以灵活地匹配文件名模式。
  • 灵活性:除了命令行,规则还可以来自文件,甚至可以分布在目录树的各个层级,实现精细化的控制。

现在,我们不仅知道 rsync 如何确定要处理哪些文件,还知道如何精确地筛选它们。但是,当 rsync 决定要同步一个文件时,它不仅仅是传输文件内容。一个文件还有很多"附加信息",比如它的权限、所有者、修改时间等等。在下一章,我们将探讨 文件属性与元数据处理,看看 rsync 是如何细致地处理这些重要的"附加信息"的。

相关推荐
重启的码农6 小时前
rsync源码解析 (3) 进程角色 (Sender/Receiver/Generator)
源码
重启的码农6 小时前
rsync源码解析 (6) 文件属性与元数据处理
源码
重启的码农6 小时前
rsync源码解析 (4) 文件列表 (File List)
源码
重启的码农6 小时前
rsync源码解析 (7) 客户端/服务器通信协议
源码
前端双越老师7 小时前
为何前端圈现在不关注源码了?
面试·前端框架·源码
RPA+AI十二工作室20 小时前
影刀RPA_抖音评价获取_源码解读
运维·机器人·自动化·源码·rpa·影刀
RPA+AI十二工作室1 天前
影刀RPA_Temu关键词取数_源码解读
大数据·自动化·源码·rpa·影刀
重启的码农1 天前
rsync源码解析 (2) 增量传输算法
源码
重启的码农1 天前
rsync源码解析 (1) 选项与配置解析
源码