3. 第一个Snakefile:规则结构,目标规则,运行snakemake命令

好,我们直接上手。这一章会用最简单的例子,带你亲手写一个Snakefile、理解它的骨架、然后跑起来。你不需要任何生物信息学背景,只需要一个终端和Python环境。

相信我,Snakemake的入门门槛比Makefile低得多——你很快就会感受到这一点。

3.1 规则结构:一个规则的完整骨架

Snakemake的每个任务,都对应一个规则(rule)。一个规则定义了:输入什么、输出什么、以及怎么生成输出。我们先解剖一个最简单的规则。

rule hello:
    output:
        "hello.txt"
    shell:
        "echo 'Hello, Snakemake!' > {output}"

这个规则叫hello。它输出一个文件hello.txt,然后通过shell命令写入一行文字。注意{output}——这是Snakemake的变量替换,它会把output里定义的路径自动填进去。

规则的三个核心要素是:

  • 规则名:rule hello:,就像函数的名称,要唯一。
  • 输入/输出:input:和output:,定义文件依赖关系。Snakemake靠输出文件来追踪哪些步骤已经完成。
  • 运行方式:shell:、run:或script:,告诉Snakemake具体怎么做。

表格看得更清楚:

组件 作用 例子
规则名 唯一标识一个规则 rule hello:
input 声明依赖文件(可选) "data/raw.txt"
output 声明输出文件 "results/clean.txt"
shell/run/script 指定执行方式 shell: "cp {input} {output}"
提示:输出文件不止是结果,更是Snakemake判断“这个规则是否已经完成”的依据。如果输出文件已存在且没有过时,Snakemake会跳过该规则。

3.2 目标规则:Snakemake怎么知道该做什么?

你运行snakemake命令时,需要告诉它一个目标文件或目标规则。Snakemake会从目标出发,反向推导出需要执行的所有规则。

看一个包含两个规则的例子:

rule generate:
    output:
        "data.txt"
    shell:
        "echo 'snakemake' > {output}"

rule process:
    input:
        "data.txt"
    output:
        "result.txt"
    shell:
        "wc -c {input} > {output}"

如果运行snakemake result.txt,Snakemake发现result.txt由process规则产生,而process又依赖data.txt……嗯,它会往前回溯,先执行generate生成data.txt,再执行process生成result.txt。这就是Snakemake的依赖推导能力。

我个人习惯把“最终产物”作为第一个规则,也就是默认目标规则。Snakemake如果不指定目标,默认执行Snakefile中第一个规则。

rule all:
    input:
        "result.txt"

rule generate:
    output:
        "data.txt"
    shell:
        ...

rule process:
    input:
        "data.txt"
    output:
        "result.txt"
    shell:
        ...

现在直接运行snakemake,它会自动以all为目标,生成result.txt。干净利落。

注意:rule all本身不产生任何实际文件,它只声明目标文件的依赖关系。如果目标文件不存在或已过时,Snakemake会重新运行。

3.3 运行snakemake命令

把上面的内容写进一个文件,命名为Snakefile(注意大写S,没有扩展名)。然后在终端里:

snakemake -n  # 先dry-run,只看计划不执行

你会看到这样的输出:

Building DAG of jobs...
Job stats:
job              count    min threads    max threads
---------       -----    -------------    -------------
all                  1                1                1
generate             1                1                1
process              1                1                1
total                3                1                1

This was a dry-run (flag -n).
The actual command would have executed 3 jobs.

确认无误后,真正执行:

snakemake -c 1  # 用1个CPU核心运行

Snakemake会依次执行两个规则,生成data.txt和result.txt。如果用-c 4,它会在可能的情况下并行运行互不依赖的任务。

常用命令速查:

命令 作用
snakemake -n 干运行,只显示计划
snakemake -c 1 用1核执行
snakemake -c 4 用4核并行执行
snakemake --forceall 强制重跑所有规则
snakemake --stats stats.json 输出运行统计到JSON文件
snakemake --dag | dot -Tpng > dag.png 生成工作流依赖图(需要Graphviz)
避坑提醒:我曾经在服务器上忘记加-c,结果Snakemake默认只用一个线程,浪费了几十核资源。养成习惯,运行前先-n,确认后再加-c。

3.4 工作流核心逻辑:从目标反向推导

Snakemake的核心机制可以用一句话概括:先有目标,再往前推。它不像传统脚本从上往下跑,而是从最终结果出发,逆向寻找所有前置任务。

下图的流程展示了这种推导逻辑:

Snakemake 工作流推导流程 目标规则 all result.txt process 规则 data.txt 推导说明 1. 用户指定目标 2. Snakemake查找 目标文件依赖 3. 发现需要result.txt 4. 找到process规则 5. 发现需要data.txt 6. 如果data.txt不存在 继续向前推导 ……直到找到源头

在这个图里,Snakemake从目标规则all出发,逆向寻找result.txt,再到process规则,再到data.txt。如果data.txt已存在,它不会执行生成它的规则。你想想看,这种惰性求值策略在多步骤工作流中能节省大量计算时间。

3.5 实践中常犯的几个错误

说几个我踩过坑的地方,你遇到了可以少走弯路:

  • 文件名拼写错误:Snakemake对文件名大小写敏感。我曾经把Result.txt写成result.txt,结果Snakemake认为目标不存在,重新跑了一遍。
  • 路径写死:不要写/home/user/data/xxx.txt这种绝对路径。用相对路径,或者从config文件读取——后面会讲。
  • shell命令引号嵌套:shell里如果包含单引号/双引号,最好用run:块调Python的subprocess,或者用shell: """..."""三引号。
  • 依赖关系不闭环:如果一个规则既没输入也没输出,Snakemake会报错。每个规则至少要有output。
核心要点:Snakefile本质是一个有向无环图(DAG)的声明式描述。每个规则是一个节点,文件依赖是边。Snakemake从目标出发,沿着边反向遍历,确定执行顺序。