4. 规则(rule)的定义与使用
Snakemake 里最核心的就是规则。说白了,规则就是告诉流程:输入什么、输出什么、以及怎么干活的代码块。刚开始用 Snakemake 时,我总把它当成普通的脚本函数来写——后来发现这么想会踩不少坑。
4.1 规则的基本结构
一条规则由名字和几个关键字段组成。规则名字在同一 Snakefile 里不能重复,我喜欢用下划线分隔,比如 fastqc_run。来看个最简的例子:
rule fastqc_run:
input:
"data/sample1.fastq"
output:
"results/fastqc/sample1_fastqc.html"
shell:
"fastqc -o results/fastqc data/sample1.fastq"
这个规则做了三件事:声明输入文件、声明输出文件、用 shell 命令执行 FastQC。你想想看,如果每次都要手写这种命令,几十个样本绝对让人崩溃。规则就是帮我们自动化重复劳动的。
规则名字一定要有意义。我见过有人用 rule a、rule b 命名…等到 debug 的时候你都不知道哪条规则失效了。
4.2 input 和 output —— 规则的“出入口”
输入和输出是 Snakemake 判断依赖关系的依据。规则可以没有输入(比如下载数据),也可以没有输出(比如清理垃圾),但绝大多数质控规则两者都要有。
输入支持多个文件,用逗号分隔。输出也一样:
rule multiqc_report:
input:
"results/fastqc/sample1_fastqc.zip",
"results/fastqc/sample2_fastqc.zip",
"results/fastqc/sample3_fastqc.zip"
output:
"results/multiqc/multiqc_report.html"
shell:
"multiqc results/fastqc -o results/multiqc"
我曾经天真地以为把输入写错了也能跑通……Snakemake 会严格检查输出文件是否存在。如果没生成,会报错并告诉你“缺少目标文件”。
如果某个规则不能提前确定输入文件数量,用 input: expand(...) 动态生成。这个我们下一章会详细讲。
4.3 通配符 —— 规则的“伸缩套件”
真正的批量处理靠的就是通配符。在 output 路径里用 {sample} 这种占位符,Snakemake 会自动把它替换成实际的名字。看这个例子:
rule fastqc_wildcard:
input:
"data/{sample}.fastq"
output:
"results/fastqc/{sample}_fastqc.html"
shell:
"fastqc -o results/fastqc data/{sample}.fastq"
这里的 {sample} 就是通配符。当你让 Snakemake 生成 results/fastqc/A_fastqc.html,它会自动把 {sample} 替换成 A。我觉得这是 Snakemake 最巧妙的设计——它像模板引擎一样工作。
通配符有几个硬性规则:
- 通配符名字必须在 input 和 output 里保持完全一致
- output 里的通配符决定了通配符从哪里“解”出来
- 多个通配符可以共存,比如
{sample}_{lane}.fastq
output 里不能出现 input 中没有的通配符。例如 output 写了 {batch}_{sample}.txt 但 input 只给了 {sample}.txt,Snakemake 会报错说通配符 {batch} 无法解析。我经常因为复制粘贴忘了改 input 而卡住。
4.4 params —— 给 shell 传参数
有时候除了输入输出,你还得给 shell 命令传送额外的参数。params 就是干这个的:
rule cutadapt_trim:
input:
"data/{sample}.fastq"
output:
"results/trimmed/{sample}_trimmed.fastq"
params:
adapter="AGATCGGAAGAGC",
minlen=30
shell:
"cutadapt -a {params.adapter} -m {params.minlen} -o {output} {input}"
params 里可以写字符串、数字、甚至是 Python 表达式。我在项目里经常用它传递样本特定的参数,比如每个样本的测序平台不同,适配器序列就不一样。
你用 {params.xxx} 在 shell 里引用它们。注意点号是取属性的方式,跟 Python 一样。
| 字段 | 作用 | 是否必须 |
|---|---|---|
| input | 声明依赖文件 | 可选 |
| output | 声明输出文件 | 可选 |
| params | 传递额外参数给 shell | 可选 |
| shell | 执行 shell 命令 | 与 run 二选一 |
| run | 执行 Python 代码 | 与 shell 二选一 |
| resources | 声明资源需求(内存、CPU) | 可选 |
| log | 指定日志文件路径 | 可选 |
4.5 shell 与 run —— 执行的方式
shell 字段直接写 bash 命令。run 字段写 Python 代码。我个人习惯:能 shell 解决的不用 Python,因为生物信息学工具基本都是命令行调用。
用 run 的情况通常是需要做一些预处理:
rule count_lines:
input:
"data/{sample}.fastq"
output:
"stats/{sample}_linecount.txt"
run:
with open(input[0]) as f:
count = sum(1 for _ in f)
with open(output[0], "w") as out:
out.write(str(count))
注意:input[0] 和 output[0] 是 Snakemake 自动注入的变量。要是 input 只有一个文件,也可以用 input 和 output 直接当字符串用,但多文件时就必须用列表索引了。
shell 命令里引用的 {input}、{output}、{params.xxx} 都是自动填充占位符。Snakemake 会用实际路径替换它们,不用你自己拼字符串。
4.6 资源的声明
跑质控最怕内存不够或 CPU 抢不过其他任务。用 resources 可以给每条规则定好资源约束:
rule fastqc_res:
input:
"data/{sample}.fastq"
output:
"results/fastqc/{sample}_fastqc.html"
resources:
mem_mb=2048,
threads=2
shell:
"fastqc -t {resources.threads} -o results/fastqc data/{sample}.fastq"
resources 不只是给人类看的——当你配合集群调度器(比如 SLURM)时,Snakemake 会自动把资源请求传递给作业提交命令。嗯,这里要注意:resources 不强制阻止你超过限制,它更像一个标签。
我曾经在集群上忘了设置 mem_mb,结果一次性提交了 20 个 FastQC 任务,每个占 4GB,直接把节点内存吃满了……从那以后我每写一条规则都会顺手加上 resources。
4.7 规则的运行与检查
写好了规则怎么跑?用 snakemake -n 做干跑,看看 Snakemake 计划怎么执行:
snakemake -n results/fastqc/A_fastqc.html
这个命令不会真的执行,只是打印执行计划。我每次改完规则都会先干跑一遍,避免写到一半发现语法错误。
更多时候,你指定最终产出文件就行:
snakemake -c1 results/fastqc/A_fastqc.html
-c1 表示用 1 个 CPU 核心。Snakemake 会自动解析依赖,从第一条规则开始执行。
上图展示了一条规则从输入到执行的完整链路。你看到了:通配符是连接输入和输出的核心,params 和 resources 只是辅助信息,最终它们都汇入 shell 或 run 执行块。
写规则不难,难的是写出能被复用、不出错的规则。我个人习惯每写完一个 rule 都试着干跑一次,检查通配符有没有拼写错误。别急着跑全流程——先让一条规则跑通,再考虑扩展。