Snakemake 工作流编写基础:规则与输入输出
讲完迁移动机,咱们得先把 Snakemake 的那套核心语法捋清楚。不然到 Nextflow 里做映射,你都不知道该对应哪个概念。我个人习惯是,先搞懂一个 rule 是怎么长出来的——输入、输出、执行体,三个部分串起来就是一个任务单元。
我在项目中第一次写 Snakemake 时,把 rule 当成 Python 函数来写,结果跑出一堆奇葩路径。后来才明白,Snakemake 的 rule 本质上是 声明式的构建块,跟 Makefile 的 target 更像。你只需要告诉它:我要什么(output),依赖什么(input),怎么生成(shell/run/script)。剩下的并行、调度,它自己管。
规则的基本骨架
一个最小化的 Snakemake rule 长这样:
rule my_step:
input:
"data/raw.txt"
output:
"results/clean.txt"
shell:
"cat {input} > {output}"
- rule 名字要唯一,后面最好加个冒号。
- input / output 是文件路径列表,也可以用命名文件(named files)。
- shell 里直接写 shell 命令,
{input}和{output}会被自动展开。
嗯,这里要注意:output 文件路径必须全部来自 input 或通配符,不能凭空生成一个不属于规则输出集的文件。否则 Snakemake 会报 missing output files 错误。
输入输出的三种模式
我个人把常见的模式分成三类,用表格列一下:
| 模式 | 语法示例 | 使用场景 |
|---|---|---|
| 直接文件 | input: "a.txt" | 简单固定文件 |
| 多文件列表 | input: ["a.txt", "b.txt"] | 需要多个输入 |
| 命名文件(字典) | input: reads=["a.fq", "b.fq"] | 在 shell/run 中用 {input.reads} 引用 |
关于输出,Snakemake 还支持 temp() 标记临时文件,或 protected() 防止被意外删除。我早期做 RNA-seq 流程时,中间 bigWig 文件占了几百 G 空间,加上 temp() 后自动清理,舒服多了。
directory() 输出可以指定整个文件夹,内容你自己管理。
通配符 —— 真正的威力
Snakemake 最让我惊艳的就是通配符(wildcard)。它能让一条 rule 处理成百上千个样本,而不需要写循环。说白了,就是用花括号占位,让 Snakemake 根据实际文件名推导出通配符的值。
rule align:
input:
"data/{sample}.fastq"
output:
"bam/{sample}.bam"
shell:
"bwa mem ref.fa {input} > {output}"
你执行 snakemake bam/A.bam,Snakemake 就会把 {sample} 绑定成 A,然后找到 data/A.fastq 来生成。如果同时指定多个目标,比如 bam/A.bam bam/B.bam,它自动展开两组任务并行跑。
- 通配符命名建议用有意义的词,比如
sample、chr、tissue。 - 多重通配符:
"results/{sample}/{chr}.vcf"两个通配符。 - 正则约束:可以用
wildcard_constraints限制通配符匹配的内容,防止误匹配。
{sample} 做通配符,结果输入文件里有 sample123_A.fastq 和 sample123_B.fastq,Snakemake 把 sample123_A 当作一个样本通配符,导致下游错乱。后来加上 wildcard_constraints: sample = "[A-Za-z0-9]+" 才解决。
执行体:shell / run / script
Snakemake 提供三种方式执行规则逻辑:
| 执行体 | 优点 | 缺点 |
|---|---|---|
| shell | 直接写命令,简洁,适合简单调用 | 复杂逻辑需要写临时脚本 |
| run | Python 代码块,可以嵌入循环、条件 | 不能直接调用外部工具,需要用 subprocess |
| script | 调用独立 R/Python 脚本,隔离性好 | 需要额外维护脚本文件 |
shell 是最常用的。注意可以用 {input}、{output} 多个引用,比如 "samtools sort {input[0]} -o {output}"。
run 里写 Python 时,可以直接访问 input、output 这些变量:
rule python_step:
input:
"data.csv"
output:
"report.txt"
run:
with open(input[0]) as f:
data = f.read()
with open(output[0], "w") as f:
f.write(data.upper())
我有时候会把一些轻量数据处理塞进 run,省得单独写脚本。但一旦逻辑超过 20 行,我个人建议还是 script 更好维护。
规则之间的依赖链
Snakemake 自动通过文件名匹配来构建 DAG。如果你有两条 rule:
rule step1:
output: "intermediate/{sample}.txt"
shell: "echo step1 > {output}"
rule step2:
input: "intermediate/{sample}.txt"
output: "final/{sample}.txt"
shell: "cat {input} > {output}"
当你请求 final/A.txt,Snakemake 会发现它依赖 intermediate/A.txt,先执行 step1 生成它,再执行 step2。这个连锁反应就是 Snakemake 流程编排的核心。
你想想看,如果 step2 的输出文件不叫 {sample}.txt 而叫 final/{sample}.report.txt,只要通配符一致,它照样能衔接。所以保持命名规范非常关键。
整体知识结构
下面这张 SVG 图把规则编写的核心要素串起来,方便你对照记忆:
几点补充建议
- input 函数 (input functions):有时文件名依赖其他规则的输出,可以用 lambda 或函数动态决定。
lambda wildcards: "data/{}.txt".format(wildcards.sample)。我通常把复杂逻辑抽到 helper 模块里。 - 参数传递:用
params传额外参数,比在 shell 里硬编码好维护。params: extra="-l 100",然后在 shell 里用{params.extra}引用。 - 资源管理:
threads: 4和resources: mem_mb=2000可以控制并发,尤其在集群上使用时,这些声明直接决定调度器提交的参数。
一句话总结本章:Snakemake 的 rule = input + output + 执行体,通过通配符实现样本级并行,通过文件名自动推导依赖关系。这一段基础打牢了,后面迁移到 Nextflow 时你就能一眼看出对应的 process、input/output channel 应该怎么设计。