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命令。这样不会在写了一半流程后发现通配符冲突,减少返工。
下面这张图展示了规则、输入、输出和通配符之间的核心关系:
写规则时,建议你跟着这个图走一遍思路:先确定输出是什么,再想输入从哪里来,最后决定用哪些通配符。这样逻辑不会乱。
个人习惯:我在output里用了多少层{},就会在input里用同样层数。如果输出是results/{sample}/{run}.txt,输入一定是data/{sample}/{run}.fastq。这个习惯帮我避免了很多通配符不匹配的错误。