4. 第一个Snakemake工作流:编写简单规则,运行snakemake

好,我们正式开始动手写代码了。

前面讲了那么多概念,什么规则、输入输出、DAG图,说白了都是纸上谈兵。这一节我们要从零搭建一个真正能跑起来的Snakemake工作流。

我自己的习惯是,学任何新工具,第一个例子一定要简单到不能再简单。不是为了炫技,而是为了确认环境没问题、流程走得通。你想想看,如果在最简单的例子上就卡住了,后面复杂的分析肯定跑不下去。

4.1 从问题出发:我们想做什么

假设我们有这么个场景:

  • 一个文本文件 data/input.txt,里面有几行文字
  • 我们想把它复制一份,生成 results/copy.txt
  • 再统计一下原文件的行数,生成 results/stats.txt

就是这么简单。但别小看它——这个流程已经包含了工作流的两个核心要素:任务依赖和数据传递。

我的建议: 第一次跑Snakemake,千万别一上来就搞复杂的生物信息学分析。先把最简单的"I/O"逻辑跑通,后面再加复杂规则才有底气。

4.2 准备项目结构

先看看我们的文件该怎么组织。Snakemake本身对项目结构没有强制要求,但我是个有点强迫症的人——我习惯把数据、脚本、结果分开,这样看起来清爽,也好维护。

创建一个项目目录,比如 my_first_workflow/,在里面建好这些文件:

my_first_workflow/
├── Snakefile          # 核心文件,定义规则
├── data/
│   └── input.txt      # 输入数据
└── results/           # 输出目录(空文件夹)

输入文件 data/input.txt 的内容随便写几行就行,比如:

这是第一行
这是第二行
这是第三行

记得保存为纯文本格式,不要加什么特殊编码,UTF-8就够了。

4.3 编写第一个Snakefile

Snakefile就是Snakemake的配置中心,所有规则都写在这里。它是用Python语法扩展的,所以你可以在里面直接写Python代码——这一点我们后面会用到。

好,打开编辑器,创建一个空白文件命名为 Snakefile,开始写我们的第一条规则:

rule copy_file:
    input:
        "data/input.txt"
    output:
        "results/copy.txt"
    shell:
        "cp {input} {output}"

来,我们一起看一下这个规则的结构:

  • rule copy_file: —— 规则的名字,自己起的,但最好有意义
  • input: —— 声明输入文件,一个或多个
  • output: —— 声明输出文件,Snakemake靠这个来推断依赖顺序
  • shell: —— 真正干活的命令,用 {input} 和 {output} 做占位符
核心要点: Snakemake的规则其实就是三件事——告诉我输入是什么,输出是什么,以及怎么从输入变到输出。剩下的依赖分析和执行顺序,它自己搞定。

4.4 再加一条规则,形成完整流程

光复制文件没什么意思,我们再加一个统计行数的规则:

rule count_lines:
    input:
        "data/input.txt"
    output:
        "results/stats.txt"
    shell:
        "wc -l {input} > {output}"

现在Snakefile里有两条规则了。但这里有个问题——这两条规则之间没有依赖关系,它们可以并行跑。如果我想要先复制、再统计,或者反过来,该怎么办?

答案很简单:通过输出文件建立起依赖链。我们修改一下:

rule count_lines:
    input:
        "results/copy.txt"    # 这里变了!依赖copy_file的输出
    output:
        "results/stats.txt"
    shell:
        "wc -l {input} > {output}"

看到了吗?count_lines 的输入变成了 results/copy.txt,而 copy_file 的输出正是这个文件。Snakemake会自动识别这个依赖关系,先跑 copy_file,再跑 count_lines。

我曾经踩过的坑: 刚学Snakemake时,我老想着在规则里手动指定执行顺序,写什么"先执行A再执行B"。后来发现根本不需要——你只需要把规则的输入输出声明清楚,Snakemake自己就能画出依赖图。千万别手动控制顺序,那是反模式。

4.5 运行Snakemake

好了,Snakefile写完了,我们来看看怎么跑它。

打开终端,切换到项目目录 my_first_workflow/,然后输入:

snakemake --cores 1

--cores 1 表示用1个CPU核心。Snakemake默认是单核运行,你可以改成 --cores 4 来并行执行没依赖关系的任务。

如果一切顺利,你会看到类似这样的输出:

Building DAG of jobs...
Using shell: /bin/bash
Provided cores: 1
Rules claiming more threads will be scaled down.
Job stats:
job           count    min threads    max threads
----------  -------  -------------  -------------
copy_file         1              1              1
count_lines       1              1              1
total             2              1              1

Select jobs to execute...
Execute 1 jobs...

[Mon Jan 15 10:00:00 2025]
rule copy_file:
    input: data/input.txt
    output: results/copy.txt
    jobid: 0
    resources: tmpdir=/tmp

Execute 1 jobs...

[Mon Jan 15 10:00:01 2025]
rule count_lines:
    input: results/copy.txt
    output: results/stats.txt
    jobid: 1
    resources: tmpdir=/tmp

Finished job 1.
1 of 1 steps (100%) done

注意看执行顺序:先跑了 copy_file,再跑 count_lines。这正是我们想要的效果。

现在检查一下 results/ 文件夹,应该有两个文件:

  • copy.txt —— 内容和 input.txt 一样
  • stats.txt —— 里面写着 3 data/input.txt(假设你写3行)
一个小习惯: 我第一次跑成功的时候,特意去检查了 copy.txt 和 input.txt 的内容是否完全一致。虽然就是一次简单的cp操作,但亲眼看到结果和预期一致,这种成就感很重要。

4.6 再跑一次,看看Snakemake的聪明之处

现在试试第二次运行同样的命令:

snakemake --cores 1

你会发现输出变成了:

Building DAG of jobs...
Nothing to be done.
All jobs have been executed successfully.

为什么会这样?因为Snakemake发现所有输出文件都已经存在,而且它们的修改时间比输入文件要新。换句话说——没必要重复执行。

这其实就是Snakemake的增量计算能力。你想想看,在真实项目中,一个数据分析流程可能要跑几个小时甚至几天。如果你只是修改了一个输入文件,或者改了一小段代码,Snakemake只会重新运行受影响的那部分规则,而不是从头到尾全跑一遍。

核心优势: 增量计算 + 自动依赖解析,这两点就是Snakemake最值钱的地方。你在工作中遇到的那种"改了A要重新跑B、C、D"的麻烦事,Snakemake全帮你搞定了。

4.7 本章核心知识图谱

下面这个图,展示的就是我们刚才搭建的工作流的完整逻辑。花一分钟看清楚,后面所有的复杂流程都是在这个基础上扩展的。

第一个Snakemake工作流:结构概览 输入文件 data/input.txt 规则1: copy_file shell: cp {input} {output} 输出: results/copy.txt 规则2: count_lines shell: wc -l {input} > {output} 输出: results/stats.txt 最终产物 results/stats.txt 中间产物 results/copy.txt 💡 规则依赖:count_lines 的输入依赖 copy_file 的输出,Snakemake自动推导执行顺序。 虚线箭头表示:一个规则的输出可以作为另一个规则的输入,形成数据流。

4.8 常见问题与避坑

写第一个Snakemake工作流时,有几个地方特别容易出错,我帮你提前扫个雷。

问题现象 可能原因 解决办法
报错 Missing input files 输入文件路径写错了,或者文件不存在 检查路径大小写、是否在正确目录下
输出文件没生成 shell命令写错了,或者输出路径写错了 直接在终端跑一下命令,确认能正常运行
规则执行顺序不对 输入输出依赖没建立起来 检查 input: 是否指向了上游规则的 output:
第二次运行说"Nothing to be done" 输出文件已经是最新的了 删除输出文件或用 -f 强制重新运行
一个小技巧: 如果你不确定Snakemake会怎么执行,可以用 snakemake -n 来做个"干跑"。它不会真的执行命令,但会打印出它计划要运行哪些规则、按什么顺序。我第一次写复杂工作流时,至少干了5次才敢真跑。

嗯,到这里,你的第一个Snakemake工作流就算完成了。虽然只是复制文件和统计行数,但你已经掌握了最核心的机制——规则定义、输入输出声明、依赖性解析、增量运行。后面的章节,我们会在这个基础上不断加东西,直到能跑完整的Seurat分析流程。