编写第一条规则:让 Snakemake 动起来
我习惯把 Snakemake 看作一个「听话的调度员」——你告诉它想要的最终文件是什么,它自己推导出该执行哪些步骤。听起来是不是有点像 Makefile?没错,Snakemake 的核心理念就是从 Make 那边继承来的,只不过用 Python 语法包装了一层。
这一章咱们就亲手写一条最简规则,跑通一个完整的工作流。目标很纯粹:输入一个原始文件,经过规则处理,输出一个新文件。等这一步跑通了,后面批量对接的复杂流程才有基础。
1. 核心概念:规则 = 输入 + 输出 + 动作
在 Snakemake 里,一切靠 规则(rule) 驱动。每条规则描述三件事:
- input:规则依赖的文件(可以是多个)
- output:规则生成的文件(也是告诉 Snakemake 哪些文件可以被其他规则引用)
- 动作(shell / run / script):真正产生输出的命令或脚本
你看,这不就是三个核心要素嘛。我第一次理解 Snakemake 时,脑子里对应的是「原料→加工→成品」,一下子就好懂了。
2. 实战:一个完整的 Snakefile
好,现在试着写一条能真正运行的规则。
2.1 准备目录与文件
先在项目根目录创建如下结构:
project/
├── Snakefile
├── data/
│ └── raw/
│ └── input.txt
└── scripts/
└── convert.py
input.txt 的内容随便写几行文字,比如:
mol1
mol2
mol3
convert.py 是一个超简单的 Python 脚本,模拟对接处理前的数据转化:
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
with open(input_file) as f:
lines = f.readlines()
with open(output_file, 'w') as f:
f.write("name,status\n")
for line in lines:
name = line.strip()
f.write(f"{name},processed\n")
2.2 编写 Snakefile
在项目根目录新建 Snakefile(注意大小写,Snakemake 默认找这个文件),写入:
rule process_data:
input:
"data/raw/input.txt"
output:
"data/processed/result.csv"
shell:
"python scripts/convert.py {input} {output}"
这里需要注意几点:
{input}和{output}是 Snakemake 自动替换的特殊关键字,分别对应规则的 input 和 output 文件路径。- output 目录
data/processed/不用提前创建,Snakemake 会自动建(如果父目录不存在会报错,但 Python 的 open 不会自动建目录——这里提前建好最保险)。 - 规则名
process_data可以随便起,但建议语义化,方便后续 debug 时一眼看懂。
3. 运行与调试:-n 和 -j
Snakemake 提供了两个最常用的命令行参数:
| 参数 | 用途 | 常见的坑 |
|---|---|---|
-n |
「干跑」(dry-run),只列出要执行哪些规则,不真正执行 shell 命令 | 有时日志不够详细,你可以加 -p 显示实际命令 |
-j N |
指定并行任务数(N 为整数,-j 1 表示单线程) |
如果规则之间有文件依赖,⚠️ 不要随意设大并行数,可能导致磁盘 I/O 竞争 |
3.1 先干跑,别急着执行
snakemake -n
终端会输出类似这样的信息:
Building DAG of jobs...
Job stats:
job count min threads max threads
----------- ------- ------------- -------------
process_data 1 1 1
total 1 1 1
[Tue Jan 9 10:00:00 2025]
rule process_data:
input: data/raw/input.txt
output: data/processed/result.csv
jobid: 0
reason: Missing output files: data/processed/result.csv
resources: tmpdir=/tmp
这里最关键的一行是 reason,它告诉你为什么这个规则被触发——通常是因为 output 文件不存在。如果 output 已经存在且比 input 新,Snakemake 会跳过这条规则。我曾经因为这个特性踩过一次坑:调整了 input 文件却忘了重新跑,结果 Snakemake 以为 output 是最新的,导致对接了一堆旧数据。从那以后我每次修改完数据都会先 snakemake -n 确认一下。
3.2 正式运行
确认干跑无误后,用 -j 开始执行:
snakemake -j 1
输出会显示每条规则的 start 和 finish 时间,以及最终产物路径。
如果一切顺利,你会看到 data/processed/result.csv 被成功创建,内容类似:
name,status
mol1,processed
mol2,processed
mol3,processed
4. 常见问题与避坑指南
- shell 命令写错路径:Snakemake 默认以 Snakefile 所在目录为工作目录。如果你的脚本放在子目录,一定要写相对路径(比如
scripts/convert.py),不要写成./scripts/convert.py有时也会引起歧义,建议统一用相对路径不带./。 - output 目录不存在:Snakemake 不会自动创建 output 的父目录(除非你在 shell 里用
mkdir -p)。我曾经有个项目,批量产生上千个输出文件,忘记建目录导致所有任务全挂。现在我习惯在规则里加一句mkdir -p $(dirname {output})。 - 文件锁冲突:如果多个 Snakemake 进程同时在同一个目录下运行,Snakemake 会尝试加锁(lock)。出现「Directory cannot be locked」时,说明有其他实例在运行,或者上次运行意外退出后锁没释放。手动删除
.snakemake/目录里的 lock 文件可解决,但记得先确认没有其他进程在写。
我的习惯:每次新建工作流时,我先写一条 all 规则作为入口,让 Snakemake 知道最终要什么文件。all 规则通常只有 input,没有 output 和 shell。这样后续添加其他规则时会特别顺手。不过这一章为了保持简单,咱们没加——等下一章讲到通配符时,我再详细介绍 all 规则。
调试小技巧:如果规则报了奇怪的错误,试试 snakemake -n -p。-p 会让 Snakemake 打印出实际要执行的 shell 命令内容,你可以在终端里手动跑一遍这个命令,看看到底是脚本逻辑问题还是路径问题。
千万别把 output 写成 input 相同的文件:Snakemake 不允许规则的 output 与 input 路径重叠(除非使用通配符+特殊配置),否则会触发循环依赖检测并报错。我刚开始写的时候犯过这种低级错误,调试了半天才发现是 output 写到了 input 同目录。
5. 小结:你已经会写第一条规则了
这一章没有涉及复杂的通配符和批量处理,但核心的规则三要素——input、output、shell——你已经完全掌握了。剩下的就是把规则的数量从「1 条」变成「多条+通配符」,这正是后续章节要展开的。
嗯,记得把今天写的 Snakefile 找个地方保存好,后面我们会基于它扩展成真正的批量对接流程。手动跑一遍试试看,见证 Snakemake 如何自动帮你管理文件和依赖,真的很有成就感。