Snakemake基础:规则定义、输入输出、目标规则

好,我们正式开始搭建流水线的核心零件——规则(rule)。

我个人觉得,Snakemake 的灵魂就在规则里。它就像一个精密的加工指令:

  • 输入什么原料?
  • 输出什么产品?
  • 用什么方法加工?

这三者构成了 Snakemake 的最小工作单元。理解透彻了,后面再复杂的流程也不过是这些单元的排列组合。

Snakemake 规则核心结构 rule mytask input: "data/raw.txt" output: "results/clean.txt" shell: "python filter.py ..."

规则的基本骨架

先看一个最简单的例子。假设我们要对原始数据进行质量过滤:

rule fastq_filter:
    input:
        "data/sample1.fastq"
    output:
        "results/sample1_clean.fastq"
    shell:
        "fastp -i {input} -o {output} -q 20"

这条规则做了三件事:

  1. 定义名字:`fastq_filter`,方便管理和排错。
  2. 声明输入输出:告诉 Snakemake 依赖关系。
  3. 执行命令:用 `shell` 指令包裹具体操作。

我在项目中遇到过不少新手把输入路径写错,结果跑了半天才发现输出文件是空的。嗯,路径一定要仔细。

规则的本质:Snakemake 通过 output 文件来推导整个依赖关系图。它会反向查找——想要 output,就得先完成 input 对应的规则。

输入input的灵活用法

输入不一定是单个文件。实际项目中,我们经常需要多个输入:

rule align_reads:
    input:
        ref="genome/hg38.fa",
        reads=["data/sample1_R1.fastq", "data/sample1_R2.fastq"]
    output:
        "results/sample1.bam"
    shell:
        "bwa mem {input.ref} {input.reads} > {output}"

为什么这里要用命名输入?说白了,就是在命令行里调用更方便。你可以用 {input.ref} 指名道姓地引用,避免出错。

小提示:当输入文件超过 3 个时,我建议全部用命名方式。这样规则的可读性会直线上升。之后别人看你的 Snakefile 也会轻松很多。

输出output与通配符

输出是规则的“身份证”。Snakemake 用输出来决定这条规则要不要跑。如果输出文件已经存在且比输入新,它就跳过。

但这里有个更强大的机制——通配符(wildcards)。

rule convert_to_bed:
    input:
        "results/{sample}.bam"
    output:
        "results/{sample}.bed"
    shell:
        "bedtools bamtobed -i {input} > {output}"

你看,{sample} 就是一个通配符。它会自动从一个全局的输入文件名里继承具体值。比如目标文件是 results/SRR123.bed,Snakemake 就会自动推导:

  • 输入为 results/SRR123.bam
  • wildcards.sample = "SRR123"

为什么要这样?你想想看,没有通配符,你就得为每个样本写一条规则,那得多恐怖。有了它,一条规则管几千个样本。

警告:通配符的名字必须在 input 和 output 中保持一致。如果你在输出里写成 {sample}.bed,输入里却写成 {sample_name}.bam,Snakemake 会报错。我曾经在这个坑里卡了半小时。

目标规则:指定执行入口

Snakemake 怎么知道该从哪条规则开始?答案是目标规则(target rule)。

默认情况下,它会执行 Snakefile 中的第一条规则。所以,我们通常把第一条规则定义成一个“总指挥”:

rule all:
    input:
        "results/SRR001.bed",
        "results/SRR002.bed",
        "results/SRR003.bed"

这条 rule all 只定义了 input,没有 output 和 shell。它的作用就是告诉 Snakemake:“嘿,我要最终得到这些文件,你帮我把前面所有依赖的规则都跑完。”

在实际项目中,我会把 rule all 的 input 写成需要批量生成的文件列表,配合通配符或函数动态生成。

规则使用的完整表格对比

组件是否必须作用注意事项
input否声明依赖文件支持列表、字典和函数
output否(有例外)声明生成文件通配符必须匹配 input
shell可选执行命令行指令与 run 互斥
run可选执行 Python 代码用于复杂逻辑处理
params否传递额外参数在 shell 中用 {params.xxx}
log否声明日志文件避免标准输出被截断

用run替换shell:什么时候用?

如果加工逻辑很简单,用 shell 就够。但如果涉及到条件判断、循环或者复杂的数据处理,我会选择 run 块:

rule quality_report:
    input:
        "results/{sample}.bam"
    output:
        "results/{sample}_report.txt"
    run:
        import subprocess
        result = subprocess.run(
            ["samtools", "flagstat", input[0]],
            capture_output=True, text=True
        )
        with open(output[0], "w") as f:
            f.write(result.stdout)

run 块里你可以写任意 Python 代码。说白了,就是把 Snakemake 当作一个“工作流调度器 + Python 脚本引擎”来用。

避坑指南:常见错误

这些年我见过很多人在规则定义上翻车,这里列几个典型的:

  • 输入文件和输出文件同目录:Snakemake 会认为输出没生成,陷入死循环。
  • 通配符未在 input 中出现:输出里有 {sample},但输入是固定路径,Snakemake 不知道 sample 的值从哪来。
  • 遗漏 rule all:默认执行第一条规则,如果那条规则不是总目标,你就只能得到中间产物。

调试技巧:当你对一条规则的执行有疑惑时,可以加 --dry-run -p 参数。Snakemake 会打印出它计划执行的每条命令,但并不会真的运行。这是我最常用的调试手段。

小结一下

规则定义的三个基石:input、output、shell/run。理解通配符和目标规则,你就掌握了 Snakemake 的调度核心。之后的章节里,我们会在这个基础上叠加参数、日志、资源控制和条件分支,但核心引擎从未改变。

你只要记住一句话:给我输出,剩下的逻辑 Snakemake 帮你推导。