第三个Snakefile:基本规则(rule)结构,输入输出定义,运行snakemake命令
好,我们正式开始写第一个真正的 Snakefile。
说实话,我刚开始学 Snakemake 的时候,最困惑的就是「rule 到底是什么」。
你想象一下:你有一堆原始数据,你想对它做质控、比对、定量……每一步都是一个处理步骤。Snakemake 把每一个处理步骤封装成一个 rule。每个 rule 只做一件事,定义好输入、输出,再加上执行命令。
就这么简单。
一个 rule 长什么样
我们先看一个最基本的例子。假设我们要对一个 FASTQ 文件做质量报告,用 FastQC。
rule fastqc:
input:
"data/sample1.fastq"
output:
"results/fastqc/sample1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
这个 rule 的名字叫 fastqc。
input 里写的是原始数据路径。
output 里写的是你要生成的结果路径。
shell 里写的就是你平时在终端敲的那条命令。注意那个 {input},它是 Snakemake 的占位符,运行时会自动替换成 input 里定义的路径。
我个人习惯把 output 放在一个独立的 results/ 目录下,这样项目结构干净。你在项目中遇到过那种所有文件混在一起的乱摊子吗?用 Snakemake 可以避免。
输入输出定义的本质
Snakemake 的核心思想是:它根据 output 文件是否存在,来判断这个 rule 是否需要执行。
什么意思呢?
假如你第一次运行,results/fastqc/sample1_fastqc.html 不存在,那它就乖乖跑 FastQC。
第二次你再加一个样本,之前已经跑过的样本它不会再跑——因为输出文件已经在了。
这就是 Snakemake 的「增量执行」。说白了,它只做必须做的事。
运行 snakemake 命令
写好了 Snakefile,怎么运行?
snakemake --cores 1
就这么一行。Snakemake 会自动读取当前目录下的 Snakefile,找到所有 rule,然后按照依赖关系决定执行顺序。
如果你想指定用 4 个核心跑,那就改成:
snakemake --cores 4
如果你只想看它打算干什么,而不真的执行,用 -n(dry run):
snakemake -n
你会看到类似这样的输出:
Building DAG of jobs...
Job stats:
job count
fastqc 1
total 1
This was a dry-run (flag -n).
嗯,每次看到 DAG(有向无环图)这个词,我就想起第一次用 Snakemake 时,被这个词吓到了。其实不用怕,DAG 就是 Snakemake 帮你画的一张「谁先谁后」的依赖图。它自己会算,你不用管。
snakemake --dag | dot -Tpng > dag.png 可以把依赖图可视化出来,方便检查逻辑。
处理多个样本:通配符登场
刚才我们只处理了一个样本 sample1。但实际项目中,你可能有几十个样本。难道要为每个样本写一个 rule?
当然不。Snakemake 的解决方案是 通配符(wildcards)。
rule fastqc:
input:
"data/{sample}.fastq"
output:
"results/fastqc/{sample}_fastqc.html"
shell:
"fastqc {input} -o results/fastqc/"
注意看,{sample} 就是通配符。它相当于一个变量。当你告诉 Snakemake 你想要 results/fastqc/sample1_fastqc.html 这个文件时,它会自动把 {sample} 替换成 sample1。
我曾经踩过一个坑:通配符名字一定要和 {} 里的完全一致。比如你写成 {sample} 和 {sampl},它会报错,而且错误信息不太直观。所以,命名规范很重要。我习惯用 {sample} 表示样本名,{run} 表示测序批次,简洁明了。
实际用例:质控流程
理论说完了,我们来写一个完整的数据质控流程。
假设你有两个样本:sample1.fastq 和 sample2.fastq。你想对每个样本跑 FastQC,然后汇总报告。
rule fastqc:
input:
"data/{sample}.fastq"
output:
"results/fastqc/{sample}_fastqc.html",
"results/fastqc/{sample}_fastqc.zip"
shell:
"fastqc {input} -o results/fastqc/"
rule multiqc:
input:
expand("results/fastqc/{sample}_fastqc.html", sample=["sample1", "sample2"])
output:
"results/multiqc_report.html"
shell:
"multiqc results/fastqc/ -o results/"
这里有两点值得注意:
- 一个 rule 可以有多个 output 文件,用逗号分隔就行。
expand()函数。它会把{sample}替换成列表里的每个值,生成一组文件路径。比如上面的expand(...)会展开成:
"results/fastqc/sample1_fastqc.html",
"results/fastqc/sample2_fastqc.html"
运行整个流程,你只需要:
snakemake --cores 2
Snakemake 会自动先跑两个 fastqc job(并行两个样本),然后再跑 multiqc。如果某个样本的 FastQC 已经跑过了,它就不会重复跑。
expand() 生成的是所有组合的完整列表。如果你样本很多,小心别把不存在的样本写进去。我曾经一次性写了 100 个样本,结果有 5 个文件不存在,Snakemake 跑了半天才报错,很浪费时间的。
本章核心结构图
下面这张图把这个流程的核心逻辑串起来了。你可以对照着看,一目了然。
避坑指南
- 路径问题:Snakemake 的
shell命令默认在当前工作目录执行。如果你的output目录不存在,FastQC 可能会报错。我的做法是提前建好目录,或者在命令里加一句mkdir -p results/fastqc/。 - 占位符大小写:
{input}和{output}是保留字,用小写。通配符名字也建议小写。我见过有人用{Sample},结果和保留字冲突,排查了半天。 - 干跑检查:每次修改 Snakefile 后,先跑
snakemake -n看看它打算干什么,再正式执行。这能省下很多调试时间。
好了,这一章就到这儿。