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 会自动解析依赖,从第一条规则开始执行。

Snakemake 规则执行流程 Snakemake 规则执行流程与依赖关系 输入 data/{sample}.fastq 规则: fastqc_wildcard 通配符: {sample} 输出 {sample}_fastqc.html params: 适配器 / 最小长度 resources: mem / threads shell / run 执行 说明 Snakemake 自动解析通配符, 匹配输入、输出并执行 shell/run。

上图展示了一条规则从输入到执行的完整链路。你看到了:通配符是连接输入和输出的核心,params 和 resources 只是辅助信息,最终它们都汇入 shell 或 run 执行块。

写规则不难,难的是写出能被复用、不出错的规则。我个人习惯每写完一个 rule 都试着干跑一次,检查通配符有没有拼写错误。别急着跑全流程——先让一条规则跑通,再考虑扩展。