在上一章 文件列表 (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/
这个命令看起来有点复杂,但它完美地诠释了过滤规则的精髓。让我们把它翻译成"行李打包"的语言:
--include='*/'
:"为了能进入子目录,先把所有目录都打包。" (这是让 rsync 能够递归进入src
等目录的关键)--include='*.c'
:"所有以.c
结尾的文件,打包。"--include='.gitignore'
:".gitignore
这个文件,打包。"--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()
函数。这个函数负责:
- 创建一个新的
filter_rule
结构体。 - 解析规则字符串(比如
+ *.c
或- *.o
),填充pattern
和rflags
字段。 - 将这个新的规则追加到
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 (未匹配,默认行为是包含)
}
这个函数的执行流程,我们可以用一个时序图来更清晰地展示:
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 是如何细致地处理这些重要的"附加信息"的。