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 为什么能自动决定执行顺序。

Snakefile 规则结构图 raw.txt (input) rule clean_text shell: tr 命令 clean.txt (output) rule convert_bam {sample}.bam → {sample}.sam sample01.bam sample01.sam rule split_by_chrom 输出: sorted.bam + .bai 箭头方向:数据流向 规则读取输入 → 产生输出 通配符 {sample} 自动匹配文件名

你可以看到,从原始输入到最终产物,每个规则只关心自己那一段输入输出。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 这些控制参数,让流水线更健壮——不过那是接下来的内容,咱们先把基础打牢。