3. 第一个 Snakefile:规则的基本结构与执行

好,咱们终于要动手写真正的 Snakefile 了。

前面两章我们搭好了环境,也理解了 Snakemake 到底想解决什么问题。这一章,我们就从零开始,写一个最简单的规则,然后把它跑起来。

你想想看,Snakemake 的核心其实就是一堆“规则”。每个规则告诉它:“我有这些输入,我要产生这些输出,我用这个命令来办到。”就这么简单。

3.1 规则的最小骨架

一个规则长得什么样子?我直接给你看个例子。这是我做第一个质控流程时写的原型:

rule fastqc_raw:
    input:
        "data/sample1.fastq"
    output:
        "results/fastqc_raw/sample1_fastqc.html"
    shell:
        "fastqc data/sample1.fastq -o results/fastqc_raw/"

看懂了吗?说白了,就是在告诉 Snakemake 三件事:

  • input:你需要什么文件才能开始干活。
  • output:你承诺会造出什么文件。
  • shell:你打算用什么命令来实现。

嗯,这里要注意。Snakemake 的规则本身不关心你 shell 里写了多复杂的命令。它只关注两样东西:文件的依赖关系和产出关系。

我个人习惯是,哪怕规则再简单,也一定把 input 和 output 写清楚。为什么?因为后面一旦规则多了起来,Snakemake 需要靠这些信息来画“依赖图”,决定先跑谁、后跑谁。

3.2 输入与输出的定义细节

输入和输出看着简单,但我建议你一开始就养成良好的组织习惯。

要素 属性 类型 说明
input 必需 路径字符串 / 列表 / 字典 声明规则运行前必须存在的文件
output 必需 路径字符串 / 列表 / 字典 声明规则运行后会生成的文件
shell 可选(与 run 二选一) 字符串 直接在 shell 中执行的命令
run 可选(与 shell 二选一) Python代码块 用 Python 写执行逻辑

我在项目中遇到过一种情况:有人把 output 写成了目录而不是具体文件名。结果 Snakemake 老是报错说“output 文件未找到”。因为 Snakemake 期望 output 是一个具体的文件路径,而不是一个文件夹。

注意:Snakemake 判断规则是否已经完成,靠的是检查 output 文件是否存在。所以 output 路径必须是明确的文件名,不能用通配符或目录替代。否则每次都会重跑。

3.3 执行你的第一个 Snakefile

好,文件写好了,怎么跑?很简单,在终端里切换到 Snakefile 所在的目录,然后敲:

snakemake -j 1

这条命令的意思是:“Snakemake,帮我跑起来,最多用 1 个 CPU 核。” 它会自动去找当前目录下名叫 Snakefile 的文件,然后解析里面的规则。

执行的时候,你会看到类似这样的输出:

Building DAG of jobs...
Using shell: /bin/bash
Provided cores: 1
Rules claiming more threads will be scaled down.
Job stats:
job            count
fastqc_raw     1
total          1

Select jobs to execute...
[Sun Mar 24 10:15:32 2025]
rule fastqc_raw:
    input: data/sample1.fastq
    output: results/fastqc_raw/sample1_fastqc.html
    jobid: 0
    reason: Missing output files: results/fastqc_raw/sample1_fastqc.html
    resources: tmpdir=/tmp

Running fastqc data/sample1.fastq -o results/fastqc_raw/
Analysis complete for sample1.fastq
[Sun Mar 24 10:15:45 2025]
Finished job 0.
1 of 1 steps (100%) done

看到了吗?Snakemake 很贴心,它会把“为什么跑这个规则”写在 reason 那一行里。上面显示的是:“因为 output 文件不存在,所以要跑。”

我第一次看到这个日志的时候,心里踏实多了。它把整个决策过程都摊开给你看。

3.4 常用执行参数一览

除了 -j,你还会经常用到下面这几个参数。我把它列成表格,方便你查阅。

参数 示例 作用
-j / --cores snakemake -j 4 指定使用的 CPU 核心数
-n snakemake -n 干跑模式,只显示计划,不实际执行
-f snakemake -f 强制重新运行指定的规则
-R snakemake -R fastqc_raw 重新运行某个规则及其下游规则
--dag snakemake --dag | dot -Tpng > dag.png 打印依赖图(需要 graphviz)
建议:每次写新规则,我都习惯先用 snakemake -n 跑一遍干跑模式。它能检查出很多低级错误,比如文件路径写错、缩进不对等。干跑模式下 Snakemake 不会真的执行命令,很安全。

3.5 规则执行的核心逻辑

这一张 SVG 图,是我自己为团队培训时画的。它展示了 Snakemake 拿到一个 Snakefile 后,脑子里的处理流程。你看完就会明白,为什么它这么擅长管理工作流。

Snakemake 规则执行核心逻辑 1. 解析 Snakefile 2. 构建 DAG 依赖图 3. 检查 output 文件是否存在 文件已存在 → 跳过 文件缺失 → 执行规则 4. 产出 output,标记完成 Snakemake 每次执行前都会重建 DAG,检查 output 文件是否已存在。只有真正需要跑的规则才会被调度。

这张图总结了 Snakemake 的“大脑”是如何工作的。每次执行,它都先解析整个 Snakefile,构建一个有向无环图(DAG),然后逐个检查 output 文件是否已经存在。缺失了,它就去跑;已经在了,它就跳过。

我曾经遇到过一个小白同事,他把 input 和 output 写反了,结果 Snakemake 一顿猛跑,越跑越多,永远跑不完。你想想看,output 不存在所以跑规则,但跑完又没有生成 output……那不就是死循环了吗?

嗯,记住一条黄金法则:output 是规则的承诺,input 是规则的需求。千万别搞反。

3.6 避坑指南:常见执行错误

写第一个 Snakefile 的时候,有几个地方特别容易栽跟头。我帮你列出来,你遇到了可以直接对照。

  • 缩进错误:Snakemake 使用 Python 风格的缩进。input、output、shell 必须对齐。我刚开始写的时候,总是不小心混用 Tab 和空格,结果解析报错。
  • 路径问题:output 目录必须事先存在,或者你的命令里要能自动创建。否则 Snakemake 会说“无法写入文件”。我记得有一次跑批量分析,忘记建目录,前100个样本白跑了。
  • 忘记写版本:如果你同时有多个 Snakefile 版本在目录下,Snakemake 默认只认名字叫 Snakefile 的那个。可以用 -s 参数指定其他文件:snakemake -s my_other_snakefile。

核心原则回顾:

  1. 每个规则必须有明确的 input 和 output。
  2. output 必须是具体的文件名,不是目录。
  3. 执行前先用 snakemake -n 做干跑检查。
  4. Snakemake 靠 output 是否存在来判断是否需要执行。

好了,现在你已经掌握了规则的基本结构和执行方法。试着在你的实验目录里建一个 Snakefile,放上一个真实的 fastq 文件,跑一下 snakemake -j 1。亲眼看到 Snakemake 帮你把质控跑完,那种感觉还是很爽的。