Snakefile 基础结构与规则定义
好,咱们进入正题。一个 Snakemake 工作流到底长什么样?说白了,它的核心就是 Snakefile。你写流程,就是写这个文件。
我刚开始用 Snakemake 的时候,总觉得它跟 Makefile 差不多。但实际上,Snakemake 的语法更贴近 Python,更灵活。今天我们就把它扒开,看看里面到底是啥。
Snakefile 的骨架
一个最简单的 Snakefile,其实就是一堆 规则(rule) 的集合。每个规则定义一件事:输入什么、输出什么、怎么干。
来,直接看代码:
rule hello_world:
output:
"hello.txt"
shell:
"echo 'Hello, Snakemake!' > hello.txt"
看到没?这个规则叫 hello_world,输出一个 hello.txt,执行的 Shell 命令就是把一句话写进去。运行的时候,Snakemake 会自动检查 hello.txt 是否存在,如果不存在,它就执行 shell 里的命令。
核心点:Snakemake 是“目标驱动”的。你告诉它要产出什么,它自己倒推需要哪些输入、运行哪些规则。
规则的三要素:input、output、shell/run
每个规则,至少要有 output。至于 input 和 shell 或 run,是常用但不是必须的。比如你可以只定义一个 output,然后用手工方式满足它。
不过,实际项目中,这三者通常一起出现。我整理了一个常用字段表:
| 字段 | 作用 | 是否必需 |
|---|---|---|
input |
指定依赖的文件或规则 | 否 |
output |
指定本规则产出的文件 | 是 |
shell |
执行 Shell 命令 | 选一 |
run |
执行 Python 代码 | 选一 |
params |
传递额外参数,如阈值、路径 | 否 |
log |
指定日志文件路径 | 否 |
threads |
设定本规则使用的线程数 | 否 |
resources |
指定内存、运行时间等资源 | 否 |
小技巧:如果你的命令特别长,或者需要条件判断,我建议用 run 写 Python 代码来调用 Shell,可读性会好很多。
通配符:让规则活起来
硬编码文件名?那只能处理一个样本。真实项目里,你有上百个样本,难道写一百条规则?
答案是用 通配符。比如:
rule align:
input:
"data/{sample}.fastq"
output:
"results/{sample}.bam"
shell:
"bwa mem ref.fa {input} | samtools sort > {output}"
这里 {sample} 就是通配符。当你让 Snakemake 生成 results/A.bam 时,它会自动把 {sample} 替换成 A,然后去找 data/A.fastq。
我一同事刚用的时候,老是在 input 里写错通配符名字。记住:input 和 output 里同名通配符必须一致,不然会报错说找不到文件。
避坑:我曾经在一个项目里,把 output 写成了 results/{sample}.sorted.bam,但 input 里用的 {sample_id}。Snakemake 匹配不上,卡了我半天。通配符名称一定要统一。
规则依赖:隐式 vs 显式
工作流最迷人的地方,就是规则之间可以联动。你想想看,一个流程里,先对原始数据做质控,再比对,再变异检测,环环相扣。
Snakemake 有两种方式表达依赖:
- 隐式依赖:规则 B 的
input文件,正好是规则 A 的output文件。Snakemake 会自动识别。 - 显式依赖:规则 B 用
rule A名字来引用,明确说“我需要先执行 A”。
看个显式依赖的例子:
rule trim:
input:
"data/{sample}.fastq"
output:
"results/{sample}.trimmed.fastq"
shell:
"cutadapt -o {output} {input}"
rule align:
input:
rules.trim.output
output:
"results/{sample}.bam"
shell:
"bwa mem ref.fa {input} | samtools sort > {output}"
这里 rules.trim.output 就是显式引用。好处是什么?如果你改了 trim 规则里的输出文件名,align 能自动感知,不用手动改路径。
我个人习惯用隐式依赖,因为更直观。但复杂流程里,显式依赖可以让依赖关系更清楚,方便排查问题。
多个输入输出怎么办?
有时候一个规则要读好几个文件,或者产出好几个文件。Snakemake 用列表或字典搞定。
rule merge:
input:
"results/A.bam",
"results/B.bam"
output:
merged="results/merged.bam",
index="results/merged.bam.bai"
shell:
"samtools merge {output.merged} {input} && samtools index {output.merged}"
注意这里 output 用了命名方式。你可以用 {output.merged} 来引用,比 {output[0]} 更清晰。运行时,Snakemake 会把 {input} 自动展开成空格分隔的文件列表。
章末框架图:Snakefile 知识体系
这一章的信息量不小,我画了个框架图,帮你把整章知识点串起来。
一点总结
Snakefile 的规则定义,其实就是这三件事:输入、输出、执行逻辑。配合通配符,一个规则能覆盖几十上百个样本。依赖关系让整个流程自动串联,你不用操心中间步骤的顺序。
我个人认为,刚开始写 Snakefile 时,不用贪多。先从一个规则开始,跑通一个样本。然后慢慢加通配符、加依赖、加参数。等一个流程稳定了,再去考虑模块化。
要点回顾:
- rule 是基本单元,必须有 output。
- 通配符 {...} 让规则可以批量处理不同样本。
- 依赖可以是隐式的(通过文件匹配)或显式的(通过 rules.xxx 引用)。
- 多个输入/输出用列表或命名方式,命名方式更清晰。
底子打好了,下一块就是怎么让这些规则更灵活?怎么传递参数、控制线程资源?这些事儿,我们后面会一步步展开。