3. Snakemake工作流结构:Snakefile、规则、输入输出、通配符

3.1 Snakefile:工作流的蓝图

每个 Snakemake 项目,起点都是一个叫 Snakefile 的文件。说白了,它就是整个工作流的「施工图纸」。Snakemake 启动时,默认在当前目录找这个文件,然后逐行解析里面的规则。

我个人习惯把 Snakefile 放在项目的根目录下,和数据、脚本、配置文件分开。这样结构清爽,找东西也方便。文件内容就是纯 Python 加规则声明——对,它本质上是 Python,所以你可以写循环、条件判断,甚至导入第三方库。

一个最简单的 Snakefile 长这样:

rule all:
    input:
        "results/final.txt"

嗯,别小看这三行,它定义了一个终极目标。Snakemake 看到 rule all,就知道最终要产出 results/final.txt,然后反向去推导需要跑哪些前置规则。

提示:Snakefile 的命名是大小写敏感的。如果你写成 snakefile 或 Snakefile.txt,Snakemake 会直接忽略它。

3.2 规则:核心执行单元

规则是 Snakemake 的「灵魂」。每个规则描述了三件事:输入什么、输出什么、怎么干。我刚开始用的时候,总觉得规则就是一个个「小脚本」,后来才意识到——它们更像是一张张「承诺书」:你给我正确的输入,我保证生成对应的输出。

规则的基本骨架:

rule <规则名称>:
    input:
        "路径/文件名"
    output:
        "路径/文件名"
    shell:
        "要执行的命令"

除了 shell,你还可以用 run 直接写 Python 代码,或者用 script 调用外部脚本。三者的选择逻辑很简单:命令短用 shell,逻辑复杂用 run,脚本太长就扔进单独文件用 script 调用。

执行方式 适用场景 示例
shell 单行命令或管道 shell: "gzip {input} > {output}"
run Python 逻辑处理 run: with open(output[0], "w") as f: ...
script 外部 R/Python 脚本 script: "scripts/analyze.py"

3.3 输入输出:规则间的纽带

规则不是孤岛。一个规则的输出,往往是另一个规则的输入。Snakemake 正是通过这种「文件依赖」自动推导出执行顺序的。你不需要告诉它先跑哪个后跑哪个——它自己看文件。

我见过不少新手把输入输出写成绝对路径,结果换个机器就崩了。我的建议是:始终用相对路径,以项目根目录为基准。这样工作流才能移植。

多个输入输出怎么定义?用列表或字典:

rule 合并样本:
    input:
        fastq1="data/sample1_R1.fastq",
        fastq2="data/sample1_R2.fastq"
    output:
        bam="results/sample1.bam",
        bai="results/sample1.bam.bai"
    shell:
        "bwa mem {input.fastq1} {input.fastq2} | samtools sort > {output.bam}"
        " && samtools index {output.bam}"

用命名的方式,代码可读性直接拉满。你想想看,哪天回头维护这个工作流,看到 {input.fastq1} 比 {input[0]} 舒服多了。

注意:Snakemake 检查文件是否存在时,不会自动创建中间目录。如果输出路径里有不存在的文件夹,记得在规则里加一句 shell: "mkdir -p $(dirname {output})",或者在 Python 里用 os.makedirs()。

3.4 通配符:动态匹配的利器

这才是 Snakemake 最「骚」的地方。没有通配符,你只能一条规则处理一个文件;有了通配符,一条规则就能处理成千上万个文件。

通配符用花括号 { } 包裹,写在输入输出文件名里。比如 data/{sample}.fastq 里的 {sample} 就是一个通配符。运行时,Snakemake 会根据实际需要输出的文件,反向解析出通配符的值。

举个例子:

rule 比对:
    input:
        "data/{sample}.fastq"
    output:
        "results/{sample}.bam"
    shell:
        "bwa mem ref.fa {input} | samtools sort > {output}"

当你执行 snakemake results/sample1.bam 时,Snakemake 自动把 {sample} 匹配成 sample1。然后它发现 data/sample1.fastq 存在,就执行比对命令。

通配符可以同时有多个:

output: "results/{sample}_{lane}.bam"

匹配时,Snakemake 会同时解析 {sample} 和 {lane} 两个变量。

避坑指南:通配符的匹配范围

我曾经在项目中遇到过一个问题:通配符匹配得太「贪心」了。比如文件名是 sample_01_clean.fastq,你用 {sample}_clean.fastq 去匹配,{sample} 会吞掉 sample_01 还是 sample_01_?

答案是:Snakemake 的匹配默认是非贪婪的,它会在满足整体模式的前提下,让每个通配符匹配最短的可能值。但为了保险,我建议在命名时加上分隔符,比如 sample_{id}.clean.fastq,这样逻辑一目了然。

核心原则:通配符的值完全由「目标文件」决定。Snakemake 从你想要的文件名出发,反向推导通配符取值,再检查对应的输入是否存在。如果输入不存在,它会继续找能生成这个输入的规则——这就是「反向依赖推导」。

3.5 整体结构一览

把上面这些概念串起来,一个典型的 Snakemake 工作流结构大致是这样的:

Snakemake 工作流核心结构 Snakefile 规则定义 · 依赖推导 · 执行入口 rule preprocess data/{sample}.fastq ↓ data/{sample}.clean rule align data/{sample}.clean ↓ results/{sample}.bam rule variant results/{sample}.bam ↓ results/{sample}.vcf 通配符解析 {sample} sample1 · sample2 · sample3 · … 📁 data/*.fastq → results/*.vcf 输入文件目录 · 输出文件目录 处理规则 分析规则 输出规则 通配符解析 文件系统 通配符注入

从上图可以清楚看到:Snakefile 统领全局,它包含多个规则;规则之间通过「输入→输出」形成数据流水线;通配符则是连接文件名与变量的桥梁,让一条规则能处理无数个样本。而你只需要告诉 Snakemake 最终想要什么文件,剩下的推导、调度、执行——它全包了。

我的经验:刚开始设计工作流时,先用纸笔画一遍「规则依赖图」。标清楚每个规则的输入从哪来,输出到哪去。画清楚了再写 Snakefile,基本不会翻车。