Snakemake基础语法:规则,输入,输出,通配符

这一章我们进入正题,聊聊Snakemake最核心的几个概念:规则(rule)、输入(input)、输出(output)和通配符(wildcards)。我最初接触这个工具时,花了不少时间在理解它的规则设计上。

规则是流程的骨架

Snakemake的规则,说白了就是定义「做什么、用什么做、产生什么」。每条规则就是一个独立的加工步骤。你可以把它想象成一条流水线上的工位——原料从左边进来,加工后从右边出去,传递给下一个工位。

规则的基本结构长这样:

rule 规则名称:
    input:
        "路径/到/输入文件"
    output:
        "路径/到/输出文件"
    shell:
        "你的shell命令"

嗯,这里要提醒一点:规则名称在同一个文件里要唯一。我见过有人把两条规则都叫qc,结果Snakemake直接报错说名称冲突了。你想想看,要是两条规则叫同一个名字,执行器根本不知道你在调哪个。

我在项目中遇到过规则之间依赖搞混的情况。特别是复杂流程中,一条规则的输出作为另一条规则的输入,一旦链断了,整个流程全挂掉。所以我养成了一个习惯:每个规则只管一件明确的事。比如fastqc_run只管跑FastQC,multiqc_aggregate只管汇总,绝不混合。

输入与输出:依赖与产出的声明

规则里的input和output,表面上看是文件路径,实际上它们在构建一个有向无环图(DAG)。Snakemake通过这个图来决定任务的执行顺序。

为什么这么说?因为规则执行前,Snakemake会检查input文件是否存在。如果不存在,它就会去找哪个规则能产生那个文件。找不到就报错,告诉你「缺原料了」。

我建议所有路径都写成相对路径,不要用绝对路径。你可能觉得写死/home/user/project/data/sample1.fastq很方便,但换台机器跑就凉了。用相对路径,配合配置文件,才是正解。

输入和输出的声明,有些细节要注意:

属性 作用 避坑提醒
input 声明规则依赖的输入文件 可以无输入,但不能依赖不存在的文件
output 声明规则产生的输出文件 必须准确,Snakemake靠它判断是否需要重跑
output唯一性 不同的规则不能产生同一个输出文件 否则Snakemake会困惑「到底谁产出这个文件?」

小技巧:输出文件名最好包含规则名称或版本号,比如results/fastqc/{sample}_fastqc.html。我曾经因为输出文件名重名,导致多个样本的结果互相覆盖,排查了一天才发现。

通配符:规则复用关键

通配符是Snakemake最灵活的设计之一。你想想看,如果一个流程要处理20个样本,难道要写20条规则,每条规则写死一个样本名?当然不。用通配符,一条规则就能覆盖所有样本。

通配符在规则中用花括号{}包裹,比如{sample}。它的值来自输出文件路径中对应的部分。举个例子:

rule fastqc_run:
    input:
        "data/{sample}.fastq"
    output:
        "results/fastqc/{sample}_fastqc.html"
    shell:
        "fastqc {input} -o results/fastqc/"

这里{sample}就是一个通配符。当你告诉Snakemake「我需要results/fastqc/S001_fastqc.html」时,Snakekmake会解析出{sample}=S001,然后把input中的{sample}替换为S001,得到data/S001.fastq。接着它才会执行那条shell命令。

我曾经因为通配符命名太随意,导致调试时根本分不清哪个通配符对应哪个变量。所以我建议通配符用有意义的名称,比如{sample}、{lane}、{chromosome},而不是{x}、{y}、{z}。命名清晰,以后看代码不糊涂。

通配符还有一些高级用法,比如{sample}只能匹配一层目录,而{sample:[\w]+}可以限制匹配规则。

通配符语法 作用 实际匹配示例
{sample} 匹配任意非路径分隔符的字符串 data/S001.fastq → sample=S001
{sample:[\w]+} 使用正则限制匹配 只匹配字母数字下划线,防止意外的符号
{sample}/{run} 多个通配符 data/S001/L001.fastq → sample=S001, run=L001

嗯,这里要特别注意:一个规则内所有通配符必须同时来自同一个文件路径。比如output: "results/{sample}/{run}.txt",你输入里面也必须有对应的{sample}和{run},否则会报错说通配符未定义。

避坑指南:我曾经写过一个规则,输出里用了{sample}和{tool},但输入只用了{sample}。结果运行时报错「WildcardError: No values for wildcard 'tool'」。那一次让我彻底明白了:输出中所有通配符,必须在输入中也能被解析,否则Snakemake不知道从哪获取值。

实战:基础规则定义

下面是一个完整的例子,结合了规则、输入、输出和通配符。这个流程处理一批FASTQ文件,先跑FastQC,再汇总:

rule all:
    input:
        expand("results/fastqc/{sample}_fastqc.html",
               sample=["S001", "S002", "S003"])

rule fastqc_run:
    input:
        "data/{sample}.fastq"
    output:
        "results/fastqc/{sample}_fastqc.html"
    shell:
        "fastqc {input} -o results/fastqc/"

rule multiqc_aggregate:
    input:
        expand("results/fastqc/{sample}_fastqc.html",
               sample=["S001", "S002", "S003"])
    output:
        "results/multiqc_report.html"
    shell:
        "multiqc results/fastqc/ -o results/"

第一条规则all是入口规则,它声明了最终需要哪些文件。当你运行snakemake时,Snakemake会看all的输入是什么,然后反向推导出需要执行哪些规则。如果没有all,默认就是文件列表中第一个规则的输出。

工作流概要:

  • 第一步,fastqc_run将每个样本的FASTQ文件转为HTML报告
  • 第二步,multiqc_aggregate收集所有HTML报告,汇总为一份综合报告
  • Snakemake自动解析依赖,按顺序执行

你会看到,每个规则都只定义了自己输入和输出的文件,核心逻辑全在shell命令里。Snakemake不关心你用什么工具,只要输入输出满足声明,它就能跑。

我个人习惯在写新流程时,先把所有规则的输入输出写出来,形成一个「空壳」。然后验证一遍通配符是否解析正确,再填入具体的shell命令。这样不会在写了一半流程后发现通配符冲突,减少返工。

下面这张图展示了规则、输入、输出和通配符之间的核心关系:

Snakemake规则核心关系图 规则 (Rule) 输入 (Input) 输出 (Output) 通配符 (Wildcards) 核心关系 规则通过输入声明依赖,通过输出声明产物。 通配符负责将一条规则「实例化」为多个具体任务。

写规则时,建议你跟着这个图走一遍思路:先确定输出是什么,再想输入从哪里来,最后决定用哪些通配符。这样逻辑不会乱。

个人习惯:我在output里用了多少层{},就会在input里用同样层数。如果输出是results/{sample}/{run}.txt,输入一定是data/{sample}/{run}.fastq。这个习惯帮我避免了很多通配符不匹配的错误。