3. 第一个Snakefile:规则结构,目标规则,运行snakemake命令
好,我们直接上手。这一章会用最简单的例子,带你亲手写一个Snakefile、理解它的骨架、然后跑起来。你不需要任何生物信息学背景,只需要一个终端和Python环境。
相信我,Snakemake的入门门槛比Makefile低得多——你很快就会感受到这一点。
3.1 规则结构:一个规则的完整骨架
Snakemake的每个任务,都对应一个规则(rule)。一个规则定义了:输入什么、输出什么、以及怎么生成输出。我们先解剖一个最简单的规则。
rule hello:
output:
"hello.txt"
shell:
"echo 'Hello, Snakemake!' > {output}"
这个规则叫hello。它输出一个文件hello.txt,然后通过shell命令写入一行文字。注意{output}——这是Snakemake的变量替换,它会把output里定义的路径自动填进去。
规则的三个核心要素是:
- 规则名:
rule hello:,就像函数的名称,要唯一。 - 输入/输出:
input:和output:,定义文件依赖关系。Snakemake靠输出文件来追踪哪些步骤已经完成。 - 运行方式:
shell:、run:或script:,告诉Snakemake具体怎么做。
表格看得更清楚:
| 组件 | 作用 | 例子 |
|---|---|---|
| 规则名 | 唯一标识一个规则 | rule hello: |
| input | 声明依赖文件(可选) | "data/raw.txt" |
| output | 声明输出文件 | "results/clean.txt" |
| shell/run/script | 指定执行方式 | shell: "cp {input} {output}" |
3.2 目标规则:Snakemake怎么知道该做什么?
你运行snakemake命令时,需要告诉它一个目标文件或目标规则。Snakemake会从目标出发,反向推导出需要执行的所有规则。
看一个包含两个规则的例子:
rule generate:
output:
"data.txt"
shell:
"echo 'snakemake' > {output}"
rule process:
input:
"data.txt"
output:
"result.txt"
shell:
"wc -c {input} > {output}"
如果运行snakemake result.txt,Snakemake发现result.txt由process规则产生,而process又依赖data.txt……嗯,它会往前回溯,先执行generate生成data.txt,再执行process生成result.txt。这就是Snakemake的依赖推导能力。
我个人习惯把“最终产物”作为第一个规则,也就是默认目标规则。Snakemake如果不指定目标,默认执行Snakefile中第一个规则。
rule all:
input:
"result.txt"
rule generate:
output:
"data.txt"
shell:
...
rule process:
input:
"data.txt"
output:
"result.txt"
shell:
...
现在直接运行snakemake,它会自动以all为目标,生成result.txt。干净利落。
rule all本身不产生任何实际文件,它只声明目标文件的依赖关系。如果目标文件不存在或已过时,Snakemake会重新运行。
3.3 运行snakemake命令
把上面的内容写进一个文件,命名为Snakefile(注意大写S,没有扩展名)。然后在终端里:
snakemake -n # 先dry-run,只看计划不执行
你会看到这样的输出:
Building DAG of jobs...
Job stats:
job count min threads max threads
--------- ----- ------------- -------------
all 1 1 1
generate 1 1 1
process 1 1 1
total 3 1 1
This was a dry-run (flag -n).
The actual command would have executed 3 jobs.
确认无误后,真正执行:
snakemake -c 1 # 用1个CPU核心运行
Snakemake会依次执行两个规则,生成data.txt和result.txt。如果用-c 4,它会在可能的情况下并行运行互不依赖的任务。
常用命令速查:
| 命令 | 作用 |
|---|---|
snakemake -n |
干运行,只显示计划 |
snakemake -c 1 |
用1核执行 |
snakemake -c 4 |
用4核并行执行 |
snakemake --forceall |
强制重跑所有规则 |
snakemake --stats stats.json |
输出运行统计到JSON文件 |
snakemake --dag | dot -Tpng > dag.png |
生成工作流依赖图(需要Graphviz) |
-c,结果Snakemake默认只用一个线程,浪费了几十核资源。养成习惯,运行前先-n,确认后再加-c。
3.4 工作流核心逻辑:从目标反向推导
Snakemake的核心机制可以用一句话概括:先有目标,再往前推。它不像传统脚本从上往下跑,而是从最终结果出发,逆向寻找所有前置任务。
下图的流程展示了这种推导逻辑:
在这个图里,Snakemake从目标规则all出发,逆向寻找result.txt,再到process规则,再到data.txt。如果data.txt已存在,它不会执行生成它的规则。你想想看,这种惰性求值策略在多步骤工作流中能节省大量计算时间。
3.5 实践中常犯的几个错误
说几个我踩过坑的地方,你遇到了可以少走弯路:
- 文件名拼写错误:Snakemake对文件名大小写敏感。我曾经把
Result.txt写成result.txt,结果Snakemake认为目标不存在,重新跑了一遍。 - 路径写死:不要写
/home/user/data/xxx.txt这种绝对路径。用相对路径,或者从config文件读取——后面会讲。 - shell命令引号嵌套:shell里如果包含单引号/双引号,最好用
run:块调Python的subprocess,或者用shell: """..."""三引号。 - 依赖关系不闭环:如果一个规则既没输入也没输出,Snakemake会报错。每个规则至少要有output。