3、Snakefile 基础:规则语法、输入输出定义、运行工作流的基本命令
说实话,我第一次打开 Snakefile 的时候,感觉它跟 Makefile 长得挺像。但用了一个星期之后,我发现它其实更“Python 化”——规则就是函数,输入输出就是参数和返回值。这一节咱们就把最基本的规则语法、怎么定义文件依赖、以及怎么跑起来讲清楚。
3.1 规则的骨架:rule 块
一个 Snakefile 文件由若干个 rule 组成。每个规则描述一个步骤:你要喂进去什么文件,要产出什么文件,以及中间的计算怎么执行。
举个最简洁的例子 —— 用 echo 从原始数据生成一个清洗后的文本:
rule clean_text:
input:
"data/raw.txt"
output:
"data/clean.txt"
shell:
"tr '[:upper:]' '[:lower:]' < {input} > {output}"
规则名 clean_text 你可以任意起,我建议用动词 + 名词的驼峰或下划线风格,比如 align_reads、call_variants。命名清楚的话,调试时能少翻不少代码。
shell 部分里的 {input} 和 {output} 是占位符,Snakemake 在执行时自动替换成实际文件路径。这就是“输入输出驱动工作流”的核心思路——你只管声明依赖关系,引擎帮你排顺序、做增量。
3.2 输入与输出:你说了算,Snakemake 帮你管
输入可以是单个文件、多个文件,甚至是通过通配符匹配的一堆文件。我个人最常用的就是通配符,因为生物信息学里样本名经常是动态的。比如:
rule convert_bam:
input:
"data/{sample}.bam"
output:
"data/{sample}.sam"
shell:
"samtools view -h {input} > {output}"
这里的 {sample} 就是通配符。当你执行 snakemake data/sample01.sam,Snakemake 会自动把 {sample} 解析成 sample01,然后去找对应的 .bam。
输出可以有多个文件,用命名输出(named output)来区分:
rule split_by_chrom:
output:
bams="results/{sample}.sorted.bam",
bai="results/{sample}.sorted.bam.bai"
shell:
"samtools sort {input} -o {output.bams} && "
"samtools index {output.bams}"
你想想看,如果只有单个输出文件,那直接用 output: 写一行就够了。但一旦需要产出多个文件(比如 BAM 和 BAI),命名输出能让后续规则更精确地引用某个产物。
results/ 或 output/ 目录下,输入统一放在 data/ 或 rawdata/。这样目录结构一眼就能看清哪些是原始数据、哪些是中间产物、哪些是最终结果。
3.3 运行你的第一条工作流
Snakefile 写好后,怎么让它干活?最简单的一条命令:
snakemake --cores 1
这会尝试构建 Snakefile 中 第一个规则 的默认输出。但更靠谱的用法是显式指定你要构建的最终文件:
snakemake results/final_report.txt --cores 4
如果你只想看看 Snakemake 会执行哪些步骤、而不用真的运行,那就加个 -n(dry-run):
snakemake -n
输出类似这样:
Building DAG of jobs...
Job counts:
clean_text ... 1
convert_bam ... 2
split_by_chrom ... 1
This was a dry-run (flag -n). The order of jobs does not indicate execution order.
DAG 是 Snakemake 的核心数据结构——它会根据输入输出把规则串成有向无环图。哪一步能并行、哪一步必须等上游产出,它自动算好,不用你操心。
还有一个我每次必用的组合:
snakemake --dry-run --quiet 2>&1 | less
这样只打印执行计划,不会刷一大屏日志。调试的时候特别好用。
/users/me/results/counts.txt),结果 CI 环境里路径对不上,工作流一直报 MissingOutputException。后来我改了习惯——所有路径都在 Snakefile 里用相对路径,或者通过 config 传入目录前缀。
3.4 规则语法速查表
| 组成部分 | 说明 | 是否必须 |
|---|---|---|
rule 名称 | 规则标识符(字母、数字、下划线) | 是 |
input | 上游产出文件或命名输入 | 否(起始规则可不写) |
output | 本规则要生成的文件(支持通配符) | 是(至少一个) |
shell / run | 执行命令(shell)或 Python 代码(run) | 二选一 |
params | 额外参数(如线程数、内存) | 否 |
threads | 本规则使用的 CPU 核数 | 否 |
有了这张表,你写新规则时就知道哪些是必填项了。我建议刚开始时只保留 input、output、shell 三件套,后续再慢慢加 params 和 threads。
3.5 理解文件依赖:一张图讲清楚
下面这张 SVG 图展示了一个典型的 Snakefile 规则结构。节点代表规则或文件,箭头代表数据流向。看懂它,你就能理解 Snakemake 为什么能自动决定执行顺序。
你可以看到,从原始输入到最终产物,每个规则只关心自己那一段输入输出。Snakemake 通过匹配文件名来“连线”——所以 输出文件的命名规则必须清晰且一致。一旦两个规则输出了同名文件,或者输入输出链条中断,DAG 就构建失败。
核心要点:写规则就是定义“输入 -> 输出”的映射关系。Snakemake 自动解析这些关系,生成执行计划。你不需要手动指定哪个规则先跑、哪个后跑——引擎从最终目标文件反向推导,找出所有需执行的规则,再按拓扑序执行。
3.6 再啰嗦几句实用命令
snakemake -j 8:同时用 8 个核跑并行任务(等价于--cores 8)。snakemake --forceall:强制重新运行所有规则,忽略已有输出。用于测试。snakemake --dag | dot -Tsvg > dag.svg:生成 DAG 的 SVG 图,可视化工作流结构。snakemake -n -r:dry-run 时显示每条规则的输入输出。
嗯,到这里你应该能写一个最简单的 Snakefile 并跑通了。下一步就是往规则里加 params、threads、log 这些控制参数,让流水线更健壮——不过那是接下来的内容,咱们先把基础打牢。