Snakefile 基础:结构、规则、文件与通配符

Snakefile,说白了就是 Snakemake 的“剧本”。你告诉它从哪读数据、怎么处理、输出什么,它就把整个流程串起来。我刚开始接触时总觉得这东西不过是个配置文件,后来项目一复杂才发现——没有清晰的 Snakefile,你根本没法管理上百个样本的处理逻辑。

Snakefile 的整体结构

一个典型的 Snakefile 由三大部分组成:配置区、辅助函数或全局变量、以及一条条规则。规则是核心,配置是调料,辅助函数是工具。我习惯把所有规则放在文件后段,前面只放全局设置和导入。

# 配置区
configfile: "config.yaml"

# 全局变量
SAMPLES = ["A", "B", "C"]

# 规则定义
rule all:
    input:
        expand("results/{sample}/report.html", sample=SAMPLES)

rule process:
    input:
        "data/{sample}.fastq"
    output:
        "results/{sample}/cleaned.fastq"
    shell:
        "fastp -i {input} -o {output}"

rule report:
    input:
        "results/{sample}/cleaned.fastq"
    output:
        "results/{sample}/report.html"
    script:
        "scripts/generate_report.py"

你看,每个规则都像一个小函数:定义输入、输出,然后告诉 Snakemake 怎么执行。这就好比你写了一个菜谱,每一步都有明确的原材料和成品。

规则(rule)的定义

规则是 Snakefile 的最小执行单元。一个规则包含三个基本要素:input(输入)、output(输出)、执行指令(shell、script、run 或 notebook)。举个例子:

rule trim_reads:
    input:
        "raw/{sample}_R1.fastq.gz",
        "raw/{sample}_R2.fastq.gz"
    output:
        "trimmed/{sample}_R1.fastq.gz",
        "trimmed/{sample}_R2.fastq.gz"
    threads: 4
    shell:
        "trimmomatic PE {input} {output} ..."

这里我设定了线程数和双端输入输出。嗯,这里要注意:threads 和 resources 是可选但很实用的字段,尤其是你跑在集群上时,不设线程数可能调度得特别慢。

规则执行方式的三种选择

  • shell:直接写命令行,适合成熟工具(如 fastp、samtools)。
  • script:调用外部 Python 或 R 脚本,适合复杂逻辑。
  • run:内嵌 Python 代码,适合短逻辑或参数组装。

我个人偏爱 shell,因为调试时可以直接复制命令到终端跑。

输入输出文件

输入输出是规则的“门牌号”。Snakemake 根据输出文件的名字自动推导依赖关系。你想想看,如果你手写 Makefile,得手动维护依赖树;而 Snakemake 通过文件名里的通配符自动匹配。比如:

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

rule analyze_chr:
    input:
        "data/chr{chrom}.vcf"
    output:
        "results/chr{chrom}_stats.txt"
    shell:
        "bcftools stats {input} > {output}"

analyze_chr 规则里的 {chrom} 就是通配符。Snakemake 看到 all 规则需要 final_summary.txt,它不会直接跑 analyze_chr,而是先看有没有别的规则能生成那个文件。没有的话……报错。所以我总会加一条 rule all 作为总入口,避免迷路。

字段作用常见陷阱
input声明规则依赖的文件路径写错或通配符不匹配会导致循环依赖
output声明规则生成的文件输出文件必须与输入文件共享同一个通配符,否则无法合并
wildcard_constraints限制通配符匹配模式不设约束可能匹配到不该匹配的目录

我曾经在一次项目中忘记在 output 里写通配符,结果 Snakemake 以为每个样本都输出同一个文件名,直接覆盖掉前面的结果。从那以后我每写一条规则都检查一遍通配符的统一性。

通配符的基本使用

通配符是 Snakefile 的魔法。它允许你用一条规则处理所有样本。最基本的写法是 {name},Snakemake 会自动从目标文件名中提取。比如你要处理样本 X、Y、Z,只要在 rule all 里用 expand() 展开:

rule all:
    input:
        expand("results/{sample}/done.txt", sample=["X", "Y", "Z"])

rule process_sample:
    input:
        "data/{sample}.csv"
    output:
        "results/{sample}/done.txt"
    shell:
        "echo processed > {output}"

这里 expand() 会生成三个路径:results/X/done.txt、results/Y/done.txt、results/Z/done.txt。Snakemake 看到 process_sample 的输出可以匹配这些路径,就会自动为每个样本跑一次。你还可以用 wildcard_constraints 限制匹配格式,比如只匹配数字:

rule filter_variants:
    input:
        "data/chr{chrom}.vcf"
    output:
        "results/chr{chrom}_filtered.vcf"
    wildcard_constraints:
        chrom = "chr[0-9]+"
    shell:
        "gatk FilterVariants -V {input} -O {output}"

为什么要加约束?我在一个批处理项目中遇到过:数据目录里有 data/chrM.vcf(线粒体),而我本来只想处理常染色体。没加约束,结果把线粒体也跑了一遍,浪费半天时间。

如果你想传递多个通配符,比如 results/{sample}_{condition}.tsv,只要保证输入和输出使用相同的通配符名称即可。Snakemake 会自动匹配对应位置。

通配符还能结合 expand() 和 zip 实现多维度组合。比如你有两个样本列表和两种处理方式,想生成所有组合:

SAMPLES = ["A", "B"]
CONDITIONS = ["t1", "t2"]
rule all:
    input:
        expand("results/{sample}_{condition}.txt",
               zip, sample=SAMPLES, condition=CONDITIONS)

这样只会生成 A_t1.txt 和 B_t2.txt,而不是笛卡尔积。这种用法在处理配对样本时特别顺手。

Snakefile 规则执行与通配符匹配流程 rule all expand({sample}.txt) process_sample 规则 data/{sample}.csv results/{sample}/done.txt shell: echo ... results/X/done.txt 等 通配符 {sample} 在 rule all 的 expand 中被实例化为实际文件名,驱动规则自动重复执行

这张图展示了我写 Snakefile 时最核心的流程:rule all 作为入口,通过 expand() 展开所有目标文件 → Snakemake 根据输出文件名反向匹配到 process_sample 规则 → 检测输入文件是否存在 → 执行 shell 指令 → 生成实际结果。通配符就是连接这些步骤的“胶水”。

总结几个实用技巧:

  • 始终保留一个 rule all 作为最终目标,否则 Snakemake 默认跑第一条规则。
  • 通配符名称在输入和输出中必须一致,大小写敏感。
  • 用 wildcard_constraints 避免匹配到意外路径。
  • 出错时加 --dry-run 看看规则计划,能省大量调试时间。