第4章:Snakemake通配符与输入输出模式

要说Snakemake最让我上头的功能,就是通配符。没有它,你只能为每个样本写一条规则——想想50个Seurat样本怎么办?手写50个rule?别开玩笑了。通配符让一条规则匹配无数个文件,管道瞬间变得灵活。

4.1 通配符匹配:规则里的变量

通配符说白了就是规则里的占位符。你写个花括号包住名字,比如 {sample},Snakemake就会在运行的时候去匹配实际的样本名。

比如这条规则:

rule process_seurat:
    input:
        "data/{sample}/raw_counts.h5"
    output:
        "results/{sample}/seurat.rds"
    shell:
        "Rscript scripts/run_seurat.R {input} {output}"

这里 {sample} 就是通配符。当你请求输出文件 results/sampleA/seurat.rds 时,Snakemake 自动把 {sample} 设为 sampleA,然后去找输入 data/sampleA/raw_counts.h5。

通配符的约束机制

默认 {sample} 匹配任意字符(不包括路径分隔符),但有时候你得限制格式。比如样本名只能由字母和数字组成:

wildcard_constraints:
    sample = "[A-Za-z0-9]+"

我在项目中遇到过这样的坑:样本名里有下划线,结果通配符把整个路径都吞了,导致规则匹配失败。后来加了约束,再也没出过这种问题。

4.2 多输入与多输出:一个规则管多个文件

单细胞管道里,一个样本往往有多个输入文件。比如两个样本的Seurat对象合并成一份:

rule merge_samples:
    input:
        s1 = "results/sampleA/seurat.rds",
        s2 = "results/sampleB/seurat.rds"
    output:
        "results/merged/combined_seurat.rds"
    shell:
        "Rscript scripts/merge_seurat.R {input.s1} {input.s2} {output}"

多输出同理:

rule split_and_qc:
    input:
        "data/bulk_counts.csv"
    output:
        qc_report = "results/qc_report.html",
        filtered = "results/filtered_counts.csv"
    shell:
        "Rscript scripts/qc_and_filter.R {input} {output.qc_report} {output.filtered}"

我个人习惯用命名输出,这样在后续规则里引用更清晰,不会搞混顺序。

扩展模式:用expand批量生成文件名

当你需要在输入里使用多个通配符的组合时,expand() 函数是你的好朋友。它的本质是笛卡尔积展开。

rule aggregate_results:
    input:
        expand("results/{sample}/{condition}/seurat.rds",
               sample = ["A","B","C"],
               condition = ["control","treat"])
    output:
        "results/all_aggregated.rds"
    shell:
        "Rscript scripts/aggregate.R {input} {output}"

这段代码会展开成:

results/A/control/seurat.rds
results/A/treat/seurat.rds
results/B/control/seurat.rds
results/B/treat/seurat.rds
results/C/control/seurat.rds
results/C/treat/seurat.rds

你想想看,如果不这样写,你得手动列6个文件,多麻烦。

小技巧:expand() 默认用笛卡尔积,但你也可以指定组合方式。比如用 zip 模式实现一一对应:expand("results/{sample}/seurat.rds", zip, sample=["A","B"], condition=["ctrl","treat"]) 只会生成 A/ctrl 和 B/treat。

4.3 模式扩展:动态获取文件列表

很多时候你根本不知道样本列表是什么,比如今天新来了数据,明天又加了一批。这时候就得用 glob_wildcards() 自动扫描目录。

用glob_wildcards发现样本

假设你的原始数据都存在 data/ 下,每个样本有自己的子目录。你可以写一个rule来收集所有样本名:

SAMPLES, = glob_wildcards("data/{sample}/raw_counts.h5")
print(SAMPLES)

glob_wildcards 会扫描文件系统中匹配该模式的所有文件,然后提取 {sample} 的值。结果是一个元组,注意后面的逗号——这是Python的元组拆包语法,我一开始总忘。

另一种常见做法是用 lambda 函数动态构造输入:

rule all:
    input:
        lambda wildcards: expand("results/{sample}/seurat.rds",
                                 sample=SAMPLES)

这样的好处是——数据目录变了,样本列表也跟着变,你根本不用改Snakefile。

4.4 避坑指南:我在哪里摔过跤

常见错误表现解决办法
通配符未在output中出现Snakemake报错:WildcardError确保所有通配符都在output里用过一次,并且是可推导的
通配符约束太宽错误地匹配了不该匹配的目录用wildcard_constraints限制正则表达式
expand时样本名含特殊字符路径解析异常使用lambda或python函数做清洗
glob_wildcards路径写错匹配不到任何文件检查路径开头是否有./或绝对路径,最好用Path对象
曾经我踩过的坑:有一次我用了 glob_wildcards("data/{sample}/*.h5"),结果样本目录下没有.h5文件,SAMPLES变成了空列表。后续规则全部报错。后来我改成先检查目录是否存在,再用glob_wildcards扫描子文件。记住:通配符匹配基于文件存在性,不要假设目录结构一定规整。

4.5 核心逻辑:通配符解析流程

下面这张图总结了Snakemake是怎么把一条带通配符的规则变成具体任务的。

Snakemake通配符解析与实例化流程 通配符解析流程 用户请求输出文件 提取通配符:{sample} 搜索已存在的输入文件 实例化:替换通配符为实际值 执行job(shell/script/run) 从output路径中 解析通配符 匹配到文件则触发规则 若不存在则继续向上搜索依赖

这张图的流程很直白:先是用户请求一个输出文件,Snakemake 从输出文件名里提取出通配符的值,然后去文件系统里找对应的输入文件。找到了就实例化规则,找不到的话它会往上找上游规则。这就是 Snakemake 的 “反向依赖解析” 机制,通配符是它运转的齿轮。

核心要点:
  • 通配符是规则的参数化入口,必须出现在输出路径中才能被解析
  • expand() 是批量生成文件名列表的利器,配合 glob_wildcards 实现动态管道
  • 多输入多输出用命名方式更清晰({input.s1})
  • 通配符约束可以避免意外的目录匹配——尤其是当样本名含点号或下划线时