Snakefile 的骨架:规则定义与执行
好,从这章开始我们正式动手写 Snakefile。说实话,我第一次看 Snakefile 时觉得它像个奇怪的混血儿——长得像 Python,又有点 Makefile 的影子。但用顺手后你会发觉,这玩意就是为生信流程量身定做的。
4.1 规则(rule)—— 工作流的原子单位
Snakefile 里一切从规则开始。每条规则定义了三个核心要素:
- input:这条规则需要什么文件
- output:这条规则产生什么文件
- shell/run/script:如何从 input 得到 output
看一个最简单的例子:
rule fastqc:
input:
"data/sample1_R1.fastq.gz"
output:
"results/fastqc/sample1_R1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/ 2>/dev/null"
这里面有个细节要注意:{input} 和 {output} 是 Snakemake 的内置变量,运行时会被自动替换成实际路径。我个人习惯在 shell 命令里用花括号引用它们,而不是硬编码文件名——这样规则才能泛化复用。
4.2 输入与输出 —— 不只是路径列表
很多初学者以为 input/output 就是写文件名,其实它们还能做更多。
4.2.1 单文件 vs 多文件
如果规则需要多个输入,用逗号分隔即可:
rule merge_fastq:
input:
r1 = "data/sample1_R1.fastq.gz",
r2 = "data/sample1_R2.fastq.gz"
output:
"results/merged/sample1.fastq.gz"
shell:
"cat {input.r1} {input.r2} > {output}"
注意这里给输入命名了(r1, r2),后面在 shell 里可以用 {input.r1} 引用。如果你不命名,Snakemake 会用 {input[0]}、{input[1]} 这种索引方式——但我建议能命名就命名,可读性提升不止一个档次。
4.2.2 通配符(wildcards)—— 工作流的灵魂
单条规则只能处理一个样本,那 50 个样本怎么办?难道写 50 条 rule?别慌,Snakemake 用通配符解决这个问题。
rule fastqc:
input:
"data/{sample}_R1.fastq.gz"
output:
"results/fastqc/{sample}_R1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
这里的 {sample} 就是一个通配符。当 Snakemake 发现目标文件 results/fastqc/sample1_R1_fastqc.html 不存在时,它会自动把 {sample} 匹配成 sample1,然后去找 data/sample1_R1.fastq.gz。说白了,通配符就是规则里的“变量”,在运行时被真正的名字替换。
{sample}。
4.3 shell 命令 —— 执行与日志控制
shell 里写的是你在命令行实际敲的东西。但要注意几点:
- Snakemake 会为每条规则创建独立的临时 shell 子进程
- 默认情况下,标准输出和错误会打印到终端;可以用
2>/dev/null或>& /dev/null静默 - 如果 shell 命令很长,可以用反斜杠换行
但更推荐的做法是:把复杂的多步骤命令写成单独的脚本(Python 或 R),然后在 rule 里用 script: 指定。这样代码更容易维护。比如:
rule alignment:
input:
r1 = "data/{sample}_R1.fastq.gz",
r2 = "data/{sample}_R2.fastq.gz"
output:
"results/bam/{sample}.sorted.bam"
script:
"scripts/align_star.py"
脚本里可以通过 snakemake.input、snakemake.output 获取文件路径。这个我们后面章节再细讲。
4.4 运行方式 —— 让工作流转起来
写好的 Snakefile 怎么跑?核心命令就一条:
snakemake --cores 4
这会执行 Snakefile 中第一个规则(如果没指定目标)。如果你只想跑特定规则,可以加在命令后面:
snakemake results/fastqc/sample1_R1_fastqc.html --cores 4
更常用的调试命令:
- 干跑(dry run):
snakemake -n—— 只显示要执行的步骤,不实际运行。必用! - 可视化 DAG:
snakemake --dag | dot -Tpng > dag.png—— 画出任务依赖图,一眼看穿流程。我每次写新流程都会先看一眼这个图。 - 强制重新运行:
snakemake -R all—— 忽略已有输出,全部重来。 - 指定工作目录:
snakemake --directory /path/to/project
4.5 规则依赖与默认目标
Snakefile 通常包含多条规则,它们通过输出-输入关系形成依赖链。比如:
rule all:
input:
"results/multiqc_report.html"
rule multiqc:
input:
expand("results/fastqc/{sample}_R1_fastqc.html", sample=SAMPLES)
output:
"results/multiqc_report.html"
shell:
"multiqc results/fastqc/ -o results/"
rule fastqc:
input:
"data/{sample}_R1.fastq.gz"
output:
"results/fastqc/{sample}_R1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
这里的 rule all 很特殊——它是整个流程的入口。Snakemake 先看 all 需要什么,然后回溯去找能够生成这些文件的规则。我习惯把 rule all 放在文件最前面,用 input 列出最终结果文件,这样运行时就只需要 snakemake --cores 4,不用再指定目标。
rule all 往往很长,包含几十个样本的最终文件。这时可以用 expand() 函数自动展开,比如 expand("results/{sample}.bam", sample=SAMPLES)。
4.6 整体流程一览
4.7 常见问题与解决
| 现象 | 原因 | 解决办法 |
|---|---|---|
提示 MissingInputException |
某个规则的 input 文件不存在 | 检查路径和通配符是否匹配;用 snakemake -n 确定哪个规则先跑 |
提示 MissingOutputException |
shell 命令没有产生预期的 output 文件 | 检查 shell 命令中输出路径是否正确,或是否被重定向 |
| 规则没执行 | output 已存在且比 input 新 | 用 snakemake -R 规则名 强制重新运行 |
嗯,这里再分享两个小技巧。第一,写规则时先别跑全流程,先在终端手动敲一遍 shell 命令确认能跑通。第二,善用 snakemake --cores 1 -n -p 打印出即将执行的每条命令,确认通配符展开正确。我曾经因为通配符写错一个字母,排查了两个小时……从那以后我每次都先干跑。
Snakefile 的语法基础就这些。现在你可以试着写一个最简单的规则,从输入输出开始,体会 Snakemake 是怎么“理解”你的意图的。