Snakefile基础结构:规则、输入、输出、运行shell命令
好,现在咱们动手写第一个Snakefile。说实话,很多人第一次看到Snakefile会觉得它像某种配置文件,其实它就是Python代码,只不过加了一些“魔力方法”。Snakemake的核心——规则,本质上就是一个作业说明书。
规则的基本骨架
一个最简单的规则长这样:
rule say_hello:
output:
"hello.txt"
shell:
"echo 'Hello, Snakemake!' > hello.txt"
每个规则都有名字,这里叫 say_hello。output 告诉Snakemake这个规则会生成什么文件,shell 就是真正要做的事。运行的时候,Snakemake会先检查 hello.txt 存不存在,如果不存在,就执行后面的shell命令。
嗯,这里要注意:规则名不重要,输出文件才重要。Snakemake是根据文件是否存在来判断要不要重新运行的,不是根据规则名。
输入与输出——建立依赖的基石
实际工作中很少有单打独斗的规则。比如你要先下载数据,再处理,再分析。这就需要有输入了。看个例子:
rule download:
output:
"data/raw.txt"
shell:
"wget -O data/raw.txt https://example.com/sample.txt"
rule process:
input:
"data/raw.txt"
output:
"data/clean.txt"
shell:
"python scripts/clean.py {input} {output}"
这里 process 规则指定了输入文件 data/raw.txt,而 download 规则恰好会生成这个文件。Snakemake会自动发现这个依赖链:当你要生成 data/clean.txt 时,它会先检查 data/raw.txt 是否存在,不存在就去执行 download 规则。
"raw.txt",结果snakemake在工作目录乱找,加上路径就清爽很多。
shell命令中的占位符
Snakemake提供了非常方便的占位符:{input} 自动展开成所有输入文件的路径,{output} 自动展开成所有输出文件的路径。如果只有一个输入一个输出,那就是对应的路径字符串。
多个输入文件怎么办?默认会以空格拼接。你也可以用 {input:q} 给每个路径加上引号,防止路径中有空格出问题。
看个综合例子:
rule fastqc:
input:
"data/sample_R1.fastq",
"data/sample_R2.fastq"
output:
"results/fastqc/sample_R1_fastqc.html",
"results/fastqc/sample_R2_fastqc.html"
shell:
"fastqc -o results/fastqc {input}"
这里 {input} 会展开成 data/sample_R1.fastq data/sample_R2.fastq,正好符合 fastqc 命令的语法。
第一个完整Snakefile
我们把前面的片段拼起来,写一个能跑通的流程。假设我们要:
- 从某个地址下载原始数据
- 用
tr命令把大写字母转成小写 - 统计行数
Snakefile如下:
rule all:
input:
"results/line_count.txt"
rule download_data:
output:
"data/raw.txt"
shell:
"echo 'HELLO SNAKEMAKE' > data/raw.txt"
rule to_lower:
input:
"data/raw.txt"
output:
"data/lower.txt"
shell:
"tr 'A-Z' 'a-z' < {input} > {output}"
rule count_lines:
input:
"data/lower.txt"
output:
"results/line_count.txt"
shell:
"wc -l {input} > {output}"
这里我加了一个 rule all。它是一种特殊规则,只指定最终要的文件,不干活。Snakemake默认会用第一条规则来确定最终目标文件。不写 rule all 的话,就要在命令行里指定目标文件:snakemake results/line_count.txt。
data/ 和 results/ 目录,Snakemake虽然会在运行shell命令时自动处理,但最好用 directory() 或先创建再写文件。另外,如果输出文件路径中的目录不存在,有些命令会报错。建议在shell命令前加 mkdir -p $(dirname {output}) 。
直接运行
保存上面的内容为 Snakefile,然后在终端运行:
snakemake -j 1
参数 -j 1 表示只用1个CPU核。如果一切顺利,你会看到Snakemake逐条执行:创建目录、运行 echo、tr、wc。最后在 results/line_count.txt 里看到行数。
再跑一次呢?Snakemake会发现所有文件都已经存在并且没有更新,直接提示 Nothing to be done. ——这就是增量构建的魅力。
Snakefile内部结构一览
下面这张图可以帮你从宏观上理解Snakefile的组织方式:
这张图展示了Snakefile的整体骨架:最上面是全局配置,中间是规则实体,通过文件依赖形成有向无环图(DAG)。rule all 作为入口告诉Snakemake最终目标,Snakemake反向推导出所有需要执行的规则。
规则中的其他常用字段
| 字段 | 作用 | 示例 |
|---|---|---|
input | 指定依赖的文件 | input: "data/raw.txt" |
output | 指定生成的文件 | output: "results/out.txt" |
shell | 要执行的shell命令 | shell: "cp {input} {output}" |
run | 用Python代码执行 | run: open(output[0],"w").write("done") |
params | 传递给shell的额外参数 | params: extra="--verbose" |
log | 日志文件(不参与依赖判断) | log: "log/process.log" |
threads | 此规则使用的CPU核数 | threads: 4 |
resources | 内存、磁盘等资源限制 | resources: mem_mb=2000 |
你会发现 shell 和 run 是互斥的——用了shell就不能用run,反之亦然。我个人推荐:能用shell搞定的就用shell,因为bioinfo命令行工具多,而且shell命令容易调试。复杂的逻辑再考虑用Python的 run 块。
shell 命令里用 {input} 和 {output} 时,Snakemake会自动处理多个文件的情况。但如果你的命令需要区分不同输入,比如一个作为参考文件,一个作为样本文件,那就在 input 里用命名输入:input: ref="genome.fa", sample="sample.fastq",然后在shell里用 {input.ref} 和 {input.sample} 分别引用。这个在后面的章节会详细讲。
总结一下本章要义
- Snakefile由规则组成,每个规则描述一个工作步骤
- 规则核心三要素:输入文件、输出文件、要执行的命令
- 输出文件是规则的“身份证”——Snakemake通过文件是否存在决定是否运行
- 规则之间通过输入/输出文件自动建立依赖,形成DAG
rule all作为默认目标,指明最终要生成的文件- 运行
snakemake -j N即可触发整个流程
写第一个Snakefile其实很简单,把你想做的每一步拆成规则,写好输入输出和命令,Snakemake帮你搞定依赖和增量运行。你在实际项目中可能一下子就有十几个规则,但只要理解了这里的核心结构,后面扩展起来非常顺手。