第三个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 的「增量执行」。说白了,它只做必须做的事。

这里有一条黄金法则:output 文件必须能被 input 文件完全推导出来。Snakemake 不会执行那些 output 已经存在的 rule,除非你强制它重新跑。

运行 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/"

这里有两点值得注意:

  1. 一个 rule 可以有多个 output 文件,用逗号分隔就行。
  2. 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 规则核心逻辑 input: data/{sample}.fastq rule fastqc output: {sample}_fastqc.html Snakemake 自动解析 {sample},确定依赖关系,按 DAG 顺序执行 输出已存在时跳过,只执行必要步骤 执行: snakemake --cores 4

避坑指南

  • 路径问题:Snakemake 的 shell 命令默认在当前工作目录执行。如果你的 output 目录不存在,FastQC 可能会报错。我的做法是提前建好目录,或者在命令里加一句 mkdir -p results/fastqc/。
  • 占位符大小写:{input} 和 {output} 是保留字,用小写。通配符名字也建议小写。我见过有人用 {Sample},结果和保留字冲突,排查了半天。
  • 干跑检查:每次修改 Snakefile 后,先跑 snakemake -n 看看它打算干什么,再正式执行。这能省下很多调试时间。

好了,这一章就到这儿。