Snakemake 工作流编写基础:规则与输入输出

讲完迁移动机,咱们得先把 Snakemake 的那套核心语法捋清楚。不然到 Nextflow 里做映射,你都不知道该对应哪个概念。我个人习惯是,先搞懂一个 rule 是怎么长出来的——输入、输出、执行体,三个部分串起来就是一个任务单元。

我在项目中第一次写 Snakemake 时,把 rule 当成 Python 函数来写,结果跑出一堆奇葩路径。后来才明白,Snakemake 的 rule 本质上是 声明式的构建块,跟 Makefile 的 target 更像。你只需要告诉它:我要什么(output),依赖什么(input),怎么生成(shell/run/script)。剩下的并行、调度,它自己管。

规则的基本骨架

一个最小化的 Snakemake rule 长这样:

rule my_step:
    input:
        "data/raw.txt"
    output:
        "results/clean.txt"
    shell:
        "cat {input} > {output}"
  • rule 名字要唯一,后面最好加个冒号。
  • input / output 是文件路径列表,也可以用命名文件(named files)。
  • shell 里直接写 shell 命令,{input} 和 {output} 会被自动展开。

嗯,这里要注意:output 文件路径必须全部来自 input 或通配符,不能凭空生成一个不属于规则输出集的文件。否则 Snakemake 会报 missing output files 错误。

输入输出的三种模式

我个人把常见的模式分成三类,用表格列一下:

模式语法示例使用场景
直接文件input: "a.txt"简单固定文件
多文件列表input: ["a.txt", "b.txt"]需要多个输入
命名文件(字典)input: reads=["a.fq", "b.fq"]在 shell/run 中用 {input.reads} 引用

关于输出,Snakemake 还支持 temp() 标记临时文件,或 protected() 防止被意外删除。我早期做 RNA-seq 流程时,中间 bigWig 文件占了几百 G 空间,加上 temp() 后自动清理,舒服多了。

小技巧:如果 output 目录不存在,Snakemake 会自动帮你创建。但 directory() 输出可以指定整个文件夹,内容你自己管理。

通配符 —— 真正的威力

Snakemake 最让我惊艳的就是通配符(wildcard)。它能让一条 rule 处理成百上千个样本,而不需要写循环。说白了,就是用花括号占位,让 Snakemake 根据实际文件名推导出通配符的值。

rule align:
    input:
        "data/{sample}.fastq"
    output:
        "bam/{sample}.bam"
    shell:
        "bwa mem ref.fa {input} > {output}"

你执行 snakemake bam/A.bam,Snakemake 就会把 {sample} 绑定成 A,然后找到 data/A.fastq 来生成。如果同时指定多个目标,比如 bam/A.bam bam/B.bam,它自动展开两组任务并行跑。

  • 通配符命名建议用有意义的词,比如 sample、chr、tissue。
  • 多重通配符:"results/{sample}/{chr}.vcf" 两个通配符。
  • 正则约束:可以用 wildcard_constraints 限制通配符匹配的内容,防止误匹配。
避坑指南:某次我在生产流程里用 {sample} 做通配符,结果输入文件里有 sample123_A.fastq 和 sample123_B.fastq,Snakemake 把 sample123_A 当作一个样本通配符,导致下游错乱。后来加上 wildcard_constraints: sample = "[A-Za-z0-9]+" 才解决。

执行体:shell / run / script

Snakemake 提供三种方式执行规则逻辑:

执行体优点缺点
shell直接写命令,简洁,适合简单调用复杂逻辑需要写临时脚本
runPython 代码块,可以嵌入循环、条件不能直接调用外部工具,需要用 subprocess
script调用独立 R/Python 脚本,隔离性好需要额外维护脚本文件

shell 是最常用的。注意可以用 {input}、{output} 多个引用,比如 "samtools sort {input[0]} -o {output}"。

run 里写 Python 时,可以直接访问 input、output 这些变量:

rule python_step:
    input:
        "data.csv"
    output:
        "report.txt"
    run:
        with open(input[0]) as f:
            data = f.read()
        with open(output[0], "w") as f:
            f.write(data.upper())

我有时候会把一些轻量数据处理塞进 run,省得单独写脚本。但一旦逻辑超过 20 行,我个人建议还是 script 更好维护。

规则之间的依赖链

Snakemake 自动通过文件名匹配来构建 DAG。如果你有两条 rule:

rule step1:
    output: "intermediate/{sample}.txt"
    shell: "echo step1 > {output}"

rule step2:
    input: "intermediate/{sample}.txt"
    output: "final/{sample}.txt"
    shell: "cat {input} > {output}"

当你请求 final/A.txt,Snakemake 会发现它依赖 intermediate/A.txt,先执行 step1 生成它,再执行 step2。这个连锁反应就是 Snakemake 流程编排的核心。

你想想看,如果 step2 的输出文件不叫 {sample}.txt 而叫 final/{sample}.report.txt,只要通配符一致,它照样能衔接。所以保持命名规范非常关键。

整体知识结构

下面这张 SVG 图把规则编写的核心要素串起来,方便你对照记忆:

Snakemake 规则核心结构 rule my_step input: "data/{s}.txt" output: "out/{s}.bam" shell / run / script 通配符 {sample} 自动从 output 路径解析 生成多个样本任务,无需循环 由文件名自动构建 DAG 输出文件必须被下游 规则直接或间接引用

几点补充建议

  • input 函数 (input functions):有时文件名依赖其他规则的输出,可以用 lambda 或函数动态决定。lambda wildcards: "data/{}.txt".format(wildcards.sample)。我通常把复杂逻辑抽到 helper 模块里。
  • 参数传递:用 params 传额外参数,比在 shell 里硬编码好维护。params: extra="-l 100",然后在 shell 里用 {params.extra} 引用。
  • 资源管理:threads: 4 和 resources: mem_mb=2000 可以控制并发,尤其在集群上使用时,这些声明直接决定调度器提交的参数。

一句话总结本章:Snakemake 的 rule = input + output + 执行体,通过通配符实现样本级并行,通过文件名自动推导依赖关系。这一段基础打牢了,后面迁移到 Nextflow 时你就能一眼看出对应的 process、input/output channel 应该怎么设计。