4. 通配符与模式匹配:单通配符、多通配符、glob通配、规则中的wildcards对象

通配符,说白了就是Snakemake能理解的一种模糊匹配规则。你告诉它“我要处理所有样本”,它就知道去找sample1、sample2、sample3……而不是傻傻地只认一个文件名。

我刚开始用Snakemake那会儿,最头疼的就是通配符。明明写对了,跑起来就是报错。后来我发现,99%的问题都出在匹配逻辑上。今天咱们把它彻底理清,以后你写规则,想怎么模糊就怎么模糊。

4.1 单通配符:最简单的模糊匹配

单通配符就是一个花括号包起来的变量名。比如{样本名}、{染色体}、{日期}。Snakemake看到这个,就知道要用实际的值去替换它。

核心语法:规则中定义输出文件时,用 {变量名} 表示通配符。

看个例子。假设你有一个RNA-seq项目,每个样本都要单独处理:

rule samtools_sort:
    input:
        "{sample}.sam"
    output:
        "{sample}.sorted.bam"
    shell:
        "samtools sort {input} -o {output}"

当你执行 snakemake sampleA.sorted.bam,Snakemake会自动把{sample}替换成sampleA。然后去找sampleA.sam作为输入。

通配符解析规则:

  • 从目标文件(最终输出)开始,逆向解析
  • 一个规则里可以有多个通配符,但同一个通配符在不同文件中的值必须一致
  • 通配符的值不能是空字符串

我曾经踩过一个坑:

注意文件名中的特殊字符 Snakemake通配符默认不支持斜杠"/",除非你用特殊语法。我有次样本名里带了斜杠,结果Snakemake把它当成了路径分隔符,整个目录结构全乱了。从那以后,我强制团队用下划线代替斜杠,世界清净了。

4.2 多通配符:一对多匹配更灵活

单通配符只能处理一个维度的变化。但实际项目里,你经常需要多个维度同时变化。比如处理不同样本的不同染色体:

rule extract_chrom:
    input:
        "data/{sample}.bam"
    output:
        "results/{sample}/{chrom}.vcf.gz"
    shell:
        "bcftools view {input} -r {chrom} -o {output}"

这意味着你可以同时遍历样本和染色体。执行 snakemake results/sample1/chr1.vcf.gz,它会自动解析出sample=1, chrom=chr1。

多通配符的使用原则:

  • 每个通配符独立匹配,互不干扰
  • 同一个规则中同名通配符必须匹配相同值
  • 通配符的解析顺序:从左到右,从上到下

这里有个陷阱:

建议:通配符命名要有语义。我见过有人用{file}这种名字,结果三个月后自己都看不懂。多用{sample}、{chr}、{date}这类有含义的名字。你想想看,维护代码时少折腾多少脑细胞。

4.3 glob通配:批量处理文件

有时候你不知道目标文件具体叫什么,或者想一次性匹配某个目录下所有符合条件的文件。这时候就需要glob通配符——也就是 wildcard_constraints 结合Python的glob模式。

Snakemake默认的glob通配是 * 和 ?:

rule merge_chunks:
    input:
        expand("chunk_{chr}.vcf.gz", chr=["chr"+str(i) for i in range(1,23)])
    output:
        "all_chr_combined.vcf.gz"
    shell:
        "bcftools concat {input} -o {output}"

但如果你想要更精细的控制,可以用 wildcard_constraints 加正则:

rule process:
    input:
        "{sample}.txt"
    output:
        "{sample}.out"
    wildcard_constraints:
        sample="sample_[0-9]+"
    shell:
        "process.py {input} {output}"

这能确保 {sample} 只匹配类似 sample_001 这样的名字,而不是你随手写的一个文件名。

glob通配的使用场景:

  • 批量处理同名不同后缀的文件
  • 处理按编号存放的数据
  • 结合Python字符串格式化生成文件列表

嗯,这里要注意:

注意性能问题 glob通配底层依赖文件系统扫描。如果目录下有几十万个文件,每次解析都会变慢。我之前有个项目,每次执行Snakemake都要等3分钟解析目录,后来改成先列出文件列表再传进去,速度提升了10倍。别等到卡了才优化。

4.4 规则中的wildcards对象

在shell命令之外,你还可以在Python代码块(run、script、input函数)里直接访问 wildcards 对象。它是个命名元组,你可以在input函数里根据通配符动态决定输入:

rule infercnv:
    input:
        lambda wildcards: f"data/{wildcards.sample}/cnv_calls.txt"
    output:
        "results/{sample}.infercnv.pdf"
    run:
        sample_name = wildcards.sample
        cmd = f"Rscript infercnv.R --sample {sample_name}"
        shell(cmd)

这里wildcards就是Snakemake自动创建的对象。它包含了当前规则解析出来的所有通配符。

wildcards对象的核心方法:

方法/属性 说明 示例
wildcards.sample 获取样本名 "sampleA"
wildcards.keys() 获取所有通配符名称 ['sample', 'chr']
wildcards.items() 获取键值对 [('sample', 'sampleA'), ('chr', 'chr1')]
wildcards.get("sample", "default") 安全取值,可设默认 "sampleA"

我个人习惯在run块开头就先把关键通配符提取出来:

rule qc_report:
    input:
        "analysis/{sample}.bam"
    output:
        "reports/{sample}.qc.html"
    run:
        sample_name = wildcards.sample
        print(f"Processing {sample_name}")
        # 这里可以调用外部脚本
        shell(f"fastqc {input} -o reports/ --extract")

这样代码更清晰,也好调试。你想想看,一旦你熟悉了这个模式,动态输入输出就是小菜一碟。

关于wildcards,有几个关键点需要记住:

总结:

  • wildcards在run和shell块中都能用,但在shell块中以 {variable} 形式
  • 通配符的值在规则的输出文件被解析前就确定了
  • 用wildcard_constraints可以约束通配符的匹配模式

我曾经写过这样一个规则,需要在run块里同时用到多个通配符:

rule merge_replicates:
    input:
        lambda wildcards: expand(
            "results/{wildcards.sample}/rep_{rep}.txt",
            rep=range(1, int(wildcards.n_replicates)+1)
        )
    output:
        "results/{sample}/merged.txt"
    params:
        rep_count = lambda wildcards: int(wildcards.n_replicates)
    run:
        with open(output[0], 'w') as out:
            for i in range(1, params.rep_count + 1):
                with open(f"results/{wildcards.sample}/rep_{i}.txt") as f:
                    out.write(f.read())

这个规则用一个通配符{n_replicates}动态决定要汇总多少个重复样本,再用{sample}指定样本。wildcards对象帮我们优雅地串联了所有信息。

Snakemake通配符匹配流程 通配符匹配流程框架 输入文件 sampleA.sam 输入文件 sampleB.sam 输入文件 sampleC.sam 通配符 {sample} 匹配 提取文件后缀 .sam → sampleA wildcards对象 sample = sampleA 规则执行处理 输出文件 {sample}.sorted.bam 多通配符:{sample}_{chr} 同时匹配样本和染色体 💡 关键总结 通配符从目标文件开始逆向解析 → 生成wildcards对象 → 用于input函数和run块 → 输出实际文件

记住这个图,它描述了数据从输入到输出,被通配符匹配、解析、最终生成结果的全过程。你写规则的时候,脑子里就过一遍这个流程,一般不会出错。