Snakemake 基础概念:Snakefile、规则、输入输出、通配符

说实话,刚接触 Snakemake 的时候,我也觉得它就是把命令行包装一下。但用久了才发现,真正撑起整个流程的是四个核心概念:Snakefile、规则 (rule)、输入/输出、通配符 (wildcards)。把这四个东西吃透,后面跟 Seurat 集成就顺了。

我记得最早自己想写一个批量跑单细胞数据的流程,不知道从哪下手。后来就是把这四个概念一个一个拆开看,才明白 Snakemake 到底在替我们做什么。

Snakefile:整个流程的“总剧本”

Snakefile 本质上就是一个 Python 文件(只不过后缀名不一样)。你写的所有规则、参数、函数都放在这里面。Snakemake 启动时会先读这个文件,然后根据里面的规则去干活。

我个人习惯把 Snakefile 放在项目根目录下,跟数据目录 data/ 和结果目录 results/ 平级。结构清晰,不容易乱。

小提示:Snakefile 里可以写 Python 代码,比如定义列表、字典,甚至写循环。它本质上是个扩展的 Python 脚本。
# 一个最简的 Snakefile
rule all:
    input: "results/final_output.txt"

rule prepare_data:
    input: "data/raw.csv"
    output: "data/processed.csv"
    shell: "python preprocess.py {input} {output}"

规则 (rule):一次转换的“说明书”

规则是 Snakemake 的最小执行单元。每个规则定义三个东西:输入是什么、输出是什么、用什么命令转换。

规则的名字随便起,但我会用 动词+名词 的格式,比如 run_qc、normalize_counts。看名字就知道这步要干嘛。

一个规则的核心结构其实只有五个字段——但我通常只用三个:input、output、shell 或 run。另外两个 params 和 log 属于进阶内容,后面章节会讲到。

字段 作用
input 规则的输入文件,可以是列表或字符串
output 规则要生成的文件,支持通配符
shell 在 Shell 里执行的命令
run 用 Python 代码执行逻辑(代替 shell)
params 额外的参数,可以在 shell/run 里引用
关键点:Snakemake 不会按照你写的顺序执行规则。它只看 输入/输出依赖,自动推导顺序。

input / output:搭依赖的“骨架”

输入和输出就是规则之间的桥梁。Snakemake 看到一个规则的输出是另一个规则的输入,就会自动排好先后关系。

举个例子:我有个规则 run_qc 输出 qc_results.tsv,另一个规则 run_filter 的输入就是 qc_results.tsv。Snakemake 会先跑 run_qc,再跑 run_filter。完全不用你写任何顺序声明。

写输入输出的时候,有两点要注意——

  • 路径尽量用相对路径(比如 data/),不要写死绝对路径,不然换个环境就崩了。
  • 文件名字要有语义,比如 {sample}_aligned.bam,方便后面引用。

我曾经在项目里写过一个规则,输入写成了 "results/{sample}/bam",结果 Snakemake 一直找不到文件。后来发现少写了文件后缀。嗯,这种坑特别容易踩。

通配符 (wildcards):“批量处理”的灵魂

通配符是 Snakemake 最厉害的地方。没有它,我们得写几十个重复的规则。有了它,一个规则就能处理所有样本。

通配符用花括号表示,比如 {sample}。Snakemake 在运行时会自动将 {sample} 替换成实际样本名。

看个典型例子:

rule align_star:
    input:
        "data/raw/{sample}_R1.fastq",
        "data/raw/{sample}_R2.fastq"
    output:
        "results/alignment/{sample}.bam"
    shell:
        "STAR --readFilesIn {input} --outFileNamePrefix results/alignment/{sample}."

如果你有一个样本叫 sample1,Snakemake 就会把 {sample} 替换成 sample1,然后执行命令。多个样本的话,只需要在 rule all 里列出每个样本要生成的文件,或者通过管道传递。

避坑提醒:通配符的名字必须一致。比如输入中用 {sample},输出也得用 {sample}。如果写成 {sample_name} 和 {sample},Snakemake 会报错,因为它无法推断对应关系。

一张图看懂规则执行流程

下面这张图把 Snakefile、规则、输入输出、通配符串联起来,展示 Snakemake 是如何一步步推导和执行的。

Snakemake 规则执行流程 Snakefile 规则 (rule) input shell / run 通配符 {sample} output Snakemake 自动推导:匹配通配符 → 生成输出 → 检查依赖 一个规则处理好一个样本,通配符让它能处理所有样本 例如:{sample} → sample1, sample2, ...

一个完整的实战片段

把上面的概念串起来,写一个真实场景:我们要对三个样本的 FASTQ 做质控、比对、定量。用 Snakemake 写出来就是:

# Snakefile
SAMPLES = ["PBMC_1", "PBMC_2", "PBMC_3"]

rule all:
    input:
        expand("results/counts/{sample}_counts.tsv", sample=SAMPLES)

rule fastqc:
    input: "data/raw/{sample}_R1.fastq"
    output: "results/qc/{sample}_fastqc.html"
    shell: "fastqc {input} -o results/qc/"

rule star_align:
    input: "data/raw/{sample}_R1.fastq"
    output: "results/alignment/{sample}.bam"
    shell: "STAR --genomeDir ref/ --readFilesIn {input} --outFileNamePrefix results/alignment/{sample}."

rule featurecounts:
    input: "results/alignment/{sample}.bam"
    output: "results/counts/{sample}_counts.tsv"
    shell: "featureCounts -a ref/annotation.gtf -o {output} {input}"

注意 rule all 里用 expand() 函数批量展开通配符,生成三个样本的输出文件列表。Snakemake 看到这些目标,就开始逆向查找依赖,自动决定先跑哪些规则。

经验之谈:不要试图把所有逻辑塞进一条 shell 命令里。规则越细,越容易调试,也越容易复用。我一个同事喜欢写“大而全”的规则,结果一个样本出错了整个流程都得重来。

避坑指南

  • 通配符名称拼写不一致:输入和输出里通配符名字必须一模一样,否则 Snakemake 会报 WildcardError。
  • 忘记写 rule all:如果不写 rule all,Snakemake 不知道最终要生成什么文件,它会把 Snakefile 里最后一个规则当作默认目标。
  • 路径写死绝对路径:换成 Linux/Mac/Windows 都可能出问题。哪怕用 os.path.join 生成相对路径也好。
  • 输出文件写成了目录:Snakemake 检查输出是否存在时,如果是目录会出错。尽量指定具体文件名,比如 results/{sample}/qc.txt 而不是 results/{sample}/。
核心总结: Snakefile 存放规则,规则定义输入→输出,通配符让一个规则处理任意样本,Snakemake 自动编排依赖。这四个概念就是之后写 Seurat 集成流水线的基石。