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 图展示了规则、输入输出和通配符是如何协作的:

Snakemake 规则结构图(含通配符展开示例) 规则模板 rule fastqc: input: “data/{sample}.fastq” output: “results/fastqc/{sample}_fastqc.html” shell: “fastqc {input} -o results/fastqc/” 通配符展开 {sample} → sampleA {sample} → sampleB {sample} → sampleC …… 实际作业1 input: data/sampleA.fastq output: .../sampleA_fastqc.html 实际作业2 input: data/sampleB.fastq output: .../sampleB_fastqc.html 实际作业3 input: data/sampleC.fastq output: .../sampleC_fastqc.html 规则间依赖(DAG 自动推导) fastqc 产出 → multiqc 消费者 (Snakemake 自行调度执行顺序) 要点:一条规则 + 通配符 = 批量生成多个 Job,依赖图自动管理执行顺序

3.5 实战避坑与经验

规则、输入输出、通配符这三板斧用熟了,很多坑其实可以提前避开。我个人总结了几个实战中经常踩的点:

误区正确做法我的亲身教训
通配符命名不统一同一规则内保持通配符名字一致有一次我把 input 写 {sample},output 写成 {id},结果报错 debug 了一下午。
输入输出路径不匹配用一致的目录结构,output 目录要先建好Snakemake 会自动创建目录,但名字拼错就尴尬了。
一条规则塞太多步骤拆成多条原子规则我见有人把比对+排序+去重写在一条规则里,重跑时浪费时间。
忘记 {wildcards} 变量的传递在 run 块里用 wildcards.sample 获取通配符值写过 params: extra = "--threads " + wc.threads 的同学要注意,通配符别漏了。
小技巧:写好规则后,先用 snakemake -n 做一次 dry-run,看看 DAG 图是不是你要的。如果有红色警告,99% 是路径或通配符写错了。
重要提醒:通配符不要用空格、中文、特殊符号。我遇到过因为样本名里有“-”导致问题的情况,建议用字母、数字、下划线。