第二讲:Snakemake 核心概念——规则、输入输出、通配符

好,咱们直接开始。上一章你见识了 Snakemake 怎么把处理流程串起来,今天我要把最核心的三个零件掰开揉碎讲清楚:规则(rule)、输入输出,还有那个让一切变灵活的通配符。

我个人习惯,学一个新工具先看它的“最小单元”长什么样。Snakemake 的最小单元就是 rule。一条 rule 就描述一件事:用什么输入,生成什么输出,中间跑什么命令。你把它想象成一个函数定义——输入是参数,输出是返回值,shell 或 script 是函数体。只不过 Snakemake 的“函数”不需要你手动调用,它自己会分析依赖图,自动决定执行顺序。

1. 规则的骨架:一条简单的 rule

来,先看一个最基础的例子,处理一个分子对接结果的日志文件:

rule extract_docking_score:
    input:
        "results/docking_{dataset}.log"
    output:
        "scores/{dataset}.txt"
    shell:
        """
        grep "Estimated Free Energy" {input} > {output}
        """

这个 rule 的名字叫 extract_docking_score,它读入一个日志文件,用 grep 把能量行抠出来,写到输出文件里。注意看,{input} 和 {output} 是 Snakemake 的占位符,实际运行时会替换成具体的文件路径。

小技巧:我刚开始写 Snakemake 的时候,经常把 input/output 的路径写错。后来我养成习惯:先写好 output 路径,再写 input——因为 Snakemake 是从 output 反推需要哪些 input 的,逻辑清晰很多。

2. 输入输出:一场漂亮的依赖推理

Snakemake 不像 Makefile 那样只靠时间戳判断是否重跑。它会检查输出文件是否存在,以及输入文件是否有更新。如果输出已经存在且比所有输入新,那这条规则就直接跳过。
嗯,这里要注意:如果你改了生成输入文件的上游规则,Snakemake 会自动级联触发重新计算。这是它最让我省心的地方。

举个例子,药物分子预处理里经常要做:
原始分子文件 → 过滤 → 标准化 → 构建指纹

我们可以写成三条规则,它们通过 input/output 自动串联:

rule filter_molecules:
    input: "raw/{dataset}.sdf"
    output: "filtered/{dataset}.sdf"
    shell: "mobatch filter.mol2 {input} {output}"

rule normalize_mols:
    input: "filtered/{dataset}.sdf"
    output: "normalized/{dataset}.sdf"
    shell: "obabel {input} -O {output} --gen3D"

rule compute_fingerprints:
    input: "normalized/{dataset}.sdf"
    output: "fingerprints/{dataset}.csv"
    shell: "python compute_fp.py {input} {output}"

你啥都不用管,Snakemake 会自动把 filter_molecules 的输出喂给 normalize_mols,再喂给 compute_fingerprints。这种依赖推理,说白了就是帮你把数据流画成了图。

概念辨析:input/output 不仅支持单个文件,还支持列表、命名输入输出,甚至可以用函数动态生成。后面几讲会细讲,但今天先把基本模型刻在脑子里。

3. 通配符:一条规则处理一百个文件

刚才的例子里,{dataset} 就是通配符。通配符可以放在文件名里,Snakemake 会根据实际请求的目标文件自动匹配。
比如你让 Snakemake 生成 fingerprints/ligand_set_A.csv,它就会把 {dataset} 替换成 ligand_set_A,然后一路往回推导:需要 normalized/ligand_set_A.sdf,再需要 filtered/ligand_set_A.sdf,最后需要 raw/ligand_set_A.sdf。

为什么会需要通配符?因为真实项目不可能只有一个数据文件。你手上有 50 个化合物库,难道写 50 条几乎一模一样的 rule?
不,用通配符一条 rule 就够了。我曾经在项目里处理过 2000 多个晶体结构文件,就靠三个通配符规则跑完全程,代码不超过 50 行。

通配符有几点要注意:

  • 通配符名称必须一致:同一条 rule 的 input 和 output 里,相同名字的通配符会被同时替换为同一个值。
  • 通配符可以多个:比如 {protein}_{ligand}.pdb,但数量必须匹配。
  • 通配符不能出现在 shell 命令的字符串里(除非你故意传递到脚本),Snakemake 会自动从文件名提取。

4. 规则依赖流程图

我把上面的例子画成一张依赖图,方便你理解规则、输入输出和通配符是怎么配合的。

Snakemake 规则依赖图(含通配符 {dataset}) fingerprints/{dataset}.csv normalized/{dataset}.sdf filtered/{dataset}.sdf raw/{dataset}.sdf raw → filtered filtered → normalized normalized → fingerprints 通配符 {dataset} 自动匹配实际文件名

图中方框是数据文件(含通配符),箭头表示规则之间的依赖传递。你只要指定最终目标(比如 fingerprints/ligand_A.csv),Snakemake 就会从右向左自动推导出需要创建哪些中间文件,以及每条规则该什么时候跑。

5. 避坑指南:输入输出与通配符的常见陷阱

用上了通配符,事情就变得灵活了,但也容易踩坑。我给你列几个我亲身遇到的:

常见错误现象我踩过的坑
输出文件未包含通配符 Snakemake 报错:Wildcards in input not found in output 有一次我把 output 写成了 scores.txt,input 里有 {dataset},结果一直报错——通配符必须出现在 output 里,否则 Snakemake 没法确定匹配的值。
多个通配符顺序错乱 生成文件名不对应 比如 output 是 {a}_{b}.csv,但 input 写成了 {b}_{a}.csv,Snakemake 会尝试用同一个值同时替换两个名字——通配符名称必须保持一致,否则匹配会乱套。
输入文件不存在且没有上游规则 Snakemake 抛出 MissingInputException 我刚开始总忘检查原始文件路径。Snakemake 不会自动创建 raw 数据,需要你自己确保文件在正确的位置,或者用 checkpoint 动态生成。
警告:通配符不能滥用。如果一条规则里有超过两个独立变化的通配符,规则数会爆炸——每多一个维度,匹配组合就翻倍。我一般控制在一到两个,再多就用配置文件或函数动态生成。

6. 规则运行的“小剧场”

你可以把 Snakemake 执行过程想象成一场舞台剧:
导演(Snakemake)拿到剧本(Snakefile),看到最终目标(一个或多个 output 文件)。然后导演开始倒着看剧本:要演好这场戏,需要哪些道具?道具又从哪来?依次往前推,直到所有前置条件都满足(原始文件已存在或可以被其他规则生成)。最后导演按依赖顺序,一条一条地喊 action。

我习惯用 snakemake -n 先来一遍“彩排”,看看执行计划是不是我想要的。比如:

snakemake -n fingerprints/ligand_A.csv

它会打印出将要执行的规则和命令,但不会真跑。这个习惯帮我避免了至少十次灾难性的重算。

7. 一点个人感悟

说实话,我学 Snakemake 花了两周才真正理解“规则即图”的思想。一开始总把 Snakemake 当成带并行功能的 Shell 脚本,后来才意识到它本质是一个数据流编程系统——你描述的是数据怎么变,而不是步骤怎么走。
一旦接受了这个设定,写起来就非常舒服。特别是在分子预处理的场景里,一个库几十万分子,每个分子要过好几个工具,人工手动简直是自杀式操作。Snakemake 让你把精力花在“怎么处理”而不是“怎么调度”上。