3. Snakemake 基础语法:规则、输入输出与通配符
做生物信息流程的,没人想一条一条地跑命令。Snakemake 之所以能火起来,就靠三个核心概念:规则、输入输出、通配符。这三样东西放在一起,你只需要写一个规则模板,就能自动批量处理成千上万个文件。我自己最早接手的 RNA-seq 项目就是靠这招把 500 多个样本一天跑完的。
3.1 规则(rule)—— 流程的基本单元
规则就是你要做的一个步骤。说白了,它告诉 Snakemake:你想用什么命令、从哪来、到哪去。
先看一个最简单的规则长什么样:
rule fastqc:
input:
"data/sample1.fastq"
output:
"results/fastqc/sample1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
嗯,非常直观。规则名叫 fastqc,输入是原始数据,输出是质控报告,执行命令就是 fastqc。你只管描述“做什么”,Snakemake 自己会安排什么时候做。
我在项目中遇到过一位同事,他把 2000 行 shell 脚本全部塞进一个 rule 里,结果依赖混乱根本跑不通。正确的做法是:一条规则只干一件事。比如 QC、比对、定量各自独立。
3.2 输入输出(input/output)—— 数据流的桥梁
input 和 output 定义了规则的上下游。Snakemake 会检查 output 文件是否存在,如果不存在就执行这条规则。如果 input 文件还没生成,它就去找到能产生这个 input 的规则先跑。
比如我们接上面的 fastqc,再写一个汇总规则:
rule multiqc:
input:
"results/fastqc/sample1_fastqc.zip"
output:
"results/multiqc_report.html"
shell:
"multiqc {input} -o results/"
你只要给 Snakemake 一个最终目标文件(比如 results/multiqc_report.html),它会自动反向推导先跑 fastqc,再跑 multiqc。是不是有点上瘾?
有一点值得注意:output 文件的路径一般用 results/ 等目录管理,不要直接放成乱糟糟的临时文件。我习惯把输出都放在 results/ 或 output/ 下面,方便清理和归档。
3.3 通配符(wildcards)—— 批量的灵魂
上面两条 rule 都写死了 sample1。如果要跑 100 个样本,难道要复制 100 条规则?当然不。用通配符 {sample} 替换掉具体的样本名:
rule fastqc:
input:
"data/{sample}.fastq"
output:
"results/fastqc/{sample}_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
现在这个规则变成了一个“模板”。当 Snakemake 需要 results/fastqc/sampleA_fastqc.html 时,它会自动把 {sample} 替换成 sampleA,然后去找 data/sampleA.fastq 是否存在。
通配符的名字可以随便起,但建议用有意义的词,比如 {sample}、{chr}、{run_id}。同一个规则里,通配符必须保持一致。比如 input 用了 {sample},output 也要用 {sample},不能改成 {id},否则 Snakemake 不知道谁是谁。
你想想看,用通配符改造后,一个规则就能覆盖所有样本。配上 expand() 函数,批量生成目标文件简直易如反掌。
3.4 规则执行与依赖解析
Snakemake 是怎么知道先跑哪个后跑哪个的呢?核心机制就是 基于输入输出的依赖图(DAG)。每条规则都声明了自己需要什么(input)和产生什么(output)。Snakemake 从你指定的最终目标出发,反向查找所有缺失的输入,直到所有依赖都满足。
来看一个经典的比对 -> 排序 -> 索引流程:
rule bwa_mem:
input:
read1 = "data/{sample}_R1.fastq",
read2 = "data/{sample}_R2.fastq",
ref = "refs/hg38.fa"
output:
"results/bam/{sample}.bam"
shell:
"bwa mem {input.ref} {input.read1} {input.read2} | samtools view -bS - > {output}"
rule sort_bam:
input:
"results/bam/{sample}.bam"
output:
"results/bam/{sample}.sorted.bam"
shell:
"samtools sort {input} -o {output}"
rule index_bam:
input:
"results/bam/{sample}.sorted.bam"
output:
"results/bam/{sample}.sorted.bam.bai"
shell:
"samtools index {input}"
当你运行 snakemake results/bam/sampleA.sorted.bam.bai 时,它会自动推导出:先跑 bwa_mem,再跑 sort_bam,最后跑 index_bam。整个过程完全不需要你关心执行顺序。
下面这张 SVG 图展示了规则、输入输出和通配符是如何协作的:
3.5 实战避坑与经验
规则、输入输出、通配符这三板斧用熟了,很多坑其实可以提前避开。我个人总结了几个实战中经常踩的点:
| 误区 | 正确做法 | 我的亲身教训 |
|---|---|---|
| 通配符命名不统一 | 同一规则内保持通配符名字一致 | 有一次我把 input 写 {sample},output 写成 {id},结果报错 debug 了一下午。 |
| 输入输出路径不匹配 | 用一致的目录结构,output 目录要先建好 | Snakemake 会自动创建目录,但名字拼错就尴尬了。 |
| 一条规则塞太多步骤 | 拆成多条原子规则 | 我见有人把比对+排序+去重写在一条规则里,重跑时浪费时间。 |
忘记 {wildcards} 变量的传递 | 在 run 块里用 wildcards.sample 获取通配符值 | 写过 params: extra = "--threads " + wc.threads 的同学要注意,通配符别漏了。 |
snakemake -n 做一次 dry-run,看看 DAG 图是不是你要的。如果有红色警告,99% 是路径或通配符写错了。