Snakefile 基础结构:规则、输入输出与通配符

写 Snakemake 流水线,说白了就是在组装一套“自动投料机”。

你告诉机器:用什么原料,执行什么工序,得到什么成品。Snakefile 就是这台机器的说明书。

这章我会把 Snakefile 最核心的三个零件拆开讲清楚:规则(rule)、输入输出文件,还有通配符(wildcards)。理解它们,你就能写出可复用的分析流程,而不是重复粘贴代码。

规则:流水线上的工位

每个 rule 就是一个工位。它接受原材料(input),执行操作(shell 或 run),产出成品(output)。

最小可用规则示例:

rule trim_reads:
    input:
        "data/sample1.fastq"
    output:
        "results/sample1_trimmed.fastq"
    shell:
        "fastp -i {input} -o {output}"

你看,非常直白。input 指向原始数据,output 指向经过 fastp 处理后的文件,shell 里写了具体命令。Snakemake 会自动检查 output 是否已经存在,如果存在就跳过,这叫增量执行。

我在项目中遇到过有人把所有规则写成一个大 shell 脚本,改个参数就得全量重跑。用 rule 切分后,哪个文件更新就只重跑哪个工位,省下大把时间。

规则本体:rule 的构成要素

一个 rule 可以包含以下字段,我按使用频次排个序:

字段 必选/可选 作用
input 可选(但通常都有) 声明输入文件或通配符模式
output 必选 声明输出文件,Snakemake 据此决定是否执行
shell 可选(与 run 二选一) shell 命令字符串,支持 {input} {output} 占位符
run 可选 Python 代码块,灵活度更高
params 可选 额外参数,可在 shell 中用 {params.name} 引用
threads 可选 指定线程数,控制并发资源
log 可选 日志文件路径,方便调试

小技巧:output 里写两个文件,Snakemake 会等到两个都生成才判定已完成。不信你试试 output: "a.txt", "b.txt",中途杀掉进程只留一个,下次重跑会重新生成两个。

输入输出:连接工位的传送带

规则之间的依赖靠文件路径自动推导。你不需要手动写"等上一步完成才执行下一步"。Snakemake 通过 input/output 的路径匹配形成 DAG(有向无环图),然后按拓扑序执行。

串联示例:质控 + 比对

rule trim:
    input:
        "data/{sample}.fastq"
    output:
        "results/trimmed/{sample}.fq"
    shell:
        "fastp -i {input} -o {output}"

rule align:
    input:
        "results/trimmed/{sample}.fq"
    output:
        "results/bam/{sample}.bam"
    shell:
        "bwa mem ref.fa {input} | samtools sort -o {output}"

当你执行 snakemake results/bam/sampleA.bam 时,Snakemake 会自动找到 trim 规则先跑,因为 align 的 input 依赖于 trim 的 output。这就叫隐式依赖。

我曾经踩过一个坑:两个规则的 output 路径一模一样,结果 Snakemake 报错说输出文件冲突。记住,output 路径必须全局唯一,否则不知道谁生成了谁。

命名规则与路径模式

我习惯把规则按处理阶段命名:trim、align、call_variants。output 路径最好带上阶段名和样本名,比如 results/bam/{sample}.bam,这样方便后期批量管理和清理。

路径里可以用 / 建子目录,Snakemake 会自动创建不存在的目录。你想想看,这省了多少 mkdir 的麻烦。

通配符:一份配方打天下

通配符是 Snakefile 的魔法。它用 {wildcard_name} 的形式嵌入文件名,让一条规则可以处理任意数量的样本。

通配符匹配示例:

rule count_reads:
    input:
        "data/{sample}.fastq"
    output:
        "counts/{sample}.count.txt"
    shell:
        "echo $(wc -l < {input}) > {output}"

执行 snakemake counts/sample1.count.txt counts/sample2.count.txt,Snakemake 会执行两次规则,一次传入 sample=sample1,一次传入 sample=sample2。通配符的值从目标文件名里自动提取出来。

说白了,通配符让规则从“一次只能处理一个文件”升级成“一次处理一组文件”。

通配符约束:避免过度匹配

有时通配符会匹配到你不想要的路径。比如 data/{sample}.fastq 可能匹配 data/subdir/sample.fastq 吗?默认不会,因为 {sample} 只匹配单个目录层级。如果想匹配跨层级,用 {sample: \\w+} 是行不通的,得用 {sample: [^/]+} 这种正则。

注意:通配符默认匹配 非斜杠字符(除非你用 {sample: .*} 匹配全路径)。我建议尽量限制匹配范围,避免不小心吞掉相邻目录。比如想匹配 data/raw 下的文件,就写 data/raw/{sample}.fastq,别写 data/{sample}.fastq 然后指望只匹配 raw 目录。

多通配符与组合

一条规则可以有多个通配符,比如同时区分样本和染色体:

rule split_chrom:
    input:
        "alignments/{sample}.bam"
    output:
        "split/{sample}/{chrom}.bam"
    shell:
        "samtools view -b {input} {chrom} > {output}"

运行时 snakemake split/sampleA/chr1.bam split/sampleB/chr22.bam,Snakemake 会自主排列组合,每个工位只执行自己需要的组合。

知识体系总览

下面这张图概括了 Snakefile 的核心逻辑。从目标文件开始,逆向推导依赖链,通配符帮你实现批量处理。

Snakefile 核心结构与执行流程 最终目标文件 规则3(最终产出) input → (通配符) → output 规则2(中间处理) 通配符拼接输入/输出 规则1(原始输入) {sample}.fastq 等原始文件 通配符批量匹配 {sample} → 自动提取 {chrom} → 组合爆炸 执行时动态解析 Snakemake 逆向推导:从目标文件出发,沿着 input→output 链条查找缺失的中间文件,直至所有依赖满足

实战:将质控流程写成 Snakefile

下面我写一个完整的质控流程 Snakefile,包含通配符、多规则串联,以及参数传递。

完整示例(用于双端测序质控):

rule fastqc_raw:
    input:
        r1 = "data/{sample}_R1.fastq.gz",
        r2 = "data/{sample}_R2.fastq.gz"
    output:
        "qc_raw/{sample}_R1_fastqc.html",
        "qc_raw/{sample}_R2_fastqc.html"
    params:
        threads = 4
    shell:
        "fastqc -t {params.threads} -o qc_raw {input.r1} {input.r2}"

rule trim_galore:
    input:
        r1 = "data/{sample}_R1.fastq.gz",
        r2 = "data/{sample}_R2.fastq.gz"
    output:
        r1_trim = "trimmed/{sample}_R1_val_1.fq.gz",
        r2_trim = "trimmed/{sample}_R2_val_2.fq.gz"
    params:
        quality = 20
    shell:
        "trim_galore --paired --quality {params.quality} -o trimmed {input.r1} {input.r2}"

rule fastqc_trimmed:
    input:
        r1 = "trimmed/{sample}_R1_val_1.fq.gz",
        r2 = "trimmed/{sample}_R2_val_2.fq.gz"
    output:
        "qc_trimmed/{sample}_R1_fastqc.html",
        "qc_trimmed/{sample}_R2_fastqc.html"
    shell:
        "fastqc -o qc_trimmed {input.r1} {input.r2}"

执行 snakemake qc_trimmed/NA12878_R2_fastqc.html -j4,Snakemake 会自动先跑 fastqc_raw,再跑 trim_galore,最后跑 fastqc_trimmed。注意看,trim_galore 的输出文件命名依赖于 trim_galore 自己的输出格式,你必须提前了解工具的命名规则,否则通配符匹配不上。

踩坑经验:我曾经因为 trim_galore 的新版本改了输出后缀,导致下游 fastqc_trimmed 的 input 永远找不到文件,报错说“MissingInput”。解决办法是在 output 里写死后缀,或者在 shell 里用 mv 重命名为统一的模式。我倾向后者:在 shell 命令后加 mv trimmed/*_{sample}*.fq.gz trimmed/{sample}.fq.gz 这样输出可控。

执行命令与观察日志

跑 Snakemake 时加 -p 打印每条命令,加 --dry-run 预览但不执行。我每次写新规则都会先干跑一次,看看依赖链是否正确。

snakemake qc_trimmed/NA12878_R2_fastqc.html -j4 -p --dry-run

输出会显示类似这样的计划:

Building DAG of jobs...
Job counts:
    count   jobs
    1       fastqc_raw
    1       trim_galore
    1       fastqc_trimmed
    3

[Mon Day 10:00:00 2024]
rule fastqc_raw:
    input: data/NA12878_R1.fastq.gz, data/NA12878_R2.fastq.gz
    output: qc_raw/NA12878_R1_fastqc.html, qc_raw/NA12878_R2_fastqc.html
    jobid: 2
    reason: Missing output files: qc_raw/NA12878_R2_fastqc.html
    wildcards: sample=NA12878
    ...

看 wildcards 那一行,Snakemake 自动提取了样本名。如果通配符没有按预期匹配,--dry-run 能最快暴露问题。

小结:你该记住什么

  • 规则是一个工位,input/output 定义了前后依赖。
  • 通配符让规则复用,用 {sample} 表示一个可变值。
  • Snakemake 自动建 DAG,从目标文件逆向推导需要执行哪些规则。
  • 先 dry-run 再跑,可以省掉很多重跑时间。

这些就是 Snakefile 的骨架。接下来你可以往骨架上挂更多功能,比如配置文件、条件分支、报告生成。但无论多复杂,底层的规则、输入输出、通配符这三个零件永远不会变。