通配符(wildcards)的使用:模式匹配、动态文件名、多重通配符
通配符这东西,说白了就是 Snakemake 里的“变量替身”。你写 Snakefile 的时候,很多文件名是未知的——样本名、染色体号、批次……靠硬编码?那不现实。我刚开始接触 Snakemake 时,就被通配符的灵活度震住了。它能让一个规则同时处理上百个文件,而且代码几乎不用改。
这一节,咱们就聊聊通配符的核心玩法:模式匹配、动态文件名,还有多重通配符。理解透这些,你的流程就能从“写死”变成“写活”。
模式匹配基础
Snakemake 通过 input 和 output 里的花括号 {...} 来识别通配符。比如:
rule align:
input:
"data/{sample}.fastq"
output:
"results/{sample}.bam"
shell:
"bwa mem ref.fa {input} > {output}"
这里的 {sample} 就是通配符。Snakemake 会从用户要生成的最终文件(比如 results/HCC1143.bam)中提取出 sample=HCC1143,然后替换到 input 中,变成 data/HCC1143.fastq。
通配符匹配的是文件名中 除路径分隔符 / 以外的任意字符。如果你有嵌套文件夹,比如 data/{group}/{sample}.fastq,那 {group} 和 {sample} 会各自匹配路径中的一段。
来看看匹配规则表:
| 通配符写法 | 匹配内容 | 示例路径 | 提取的值 |
|---|---|---|---|
| {sample} | 单个文件夹/文件名片段(不含 /) | data/HCC1143.fastq | HCC1143 |
| {chr}_{pos} | 两个通配符,用下划线分隔 | chr1_1000.vcf | chr=chr1, pos=1000 |
| {run}_{lane}.{sample} | 三个通配符混合 | run01_lane2.HCC1143.bam | run=run01, lane=lane2, sample=HCC1143 |
嗯,这里要注意:通配符不能包含路径分隔符,所以如果你的文件名里带了 /,Snakemake 会直接报错。解决办法是提前把路径拆成多个通配符,或者用 glob_wildcards 辅助。
动态文件名实战
动态文件名指的是:output 里包含通配符,但 input 的路径需要根据通配符进行转换或查找。最常见的是 依赖其他规则输出的文件名。举个例子:
rule trim:
input:
"data/{sample}_R1.fastq",
"data/{sample}_R2.fastq"
output:
"trimmed/{sample}_trimmed_R1.fastq",
"trimmed/{sample}_trimmed_R2.fastq"
shell:
"cutadapt -o {output[0]} -p {output[1]} {input}"
rule align:
input:
r1 = "trimmed/{sample}_trimmed_R1.fastq",
r2 = "trimmed/{sample}_trimmed_R2.fastq"
output:
"bams/{sample}.bam"
shell:
"bwa mem ref.fa {input.r1} {input.r2} > {output}"
看到了吗?align 规则的 input 里也用了通配符 {sample},Snakemake 会自动从 output 中提取 sample 值,然后把它代入到 input 里,找到对应的 trim 输出文件。这就是动态文件名的精髓——通配符沿着依赖链自动传递。
核心机制:Snakemake 通过目标文件(target)解析通配符,然后反向推导所有上游输入。你不需要手动写循环,它自己会搞定。
但有个坑:如果同一个规则里有两个不同的通配符,但 input 里只出现一个,Snakemake 会报“unused wildcards”错误。举个例子:
rule bad_rule:
input:
"data/{sample}.txt"
output:
"results/{sample}_{type}.txt" # 这里 type 没在 input 里出现
shell:
"cat {input} > {output}"
运行时会报 WildcardError: Wildcards in output not used in input: type。解决办法是让 input 也包含那个通配符,或者用 params 传递额外变量。
多重通配符与冲突处理
当你的 output 包含两个或以上通配符时,Snakemake 如何解析?它用 文件名匹配优先级:先固定文字部分,然后从左到右贪婪匹配。比如:
output: "results/{project}_{sample}.bam"
target: "results/ProjectX_Sample1.bam"
Snakemake 会将 ProjectX 匹配到 {project},Sample1 匹配到 {sample}。但如果文件名有歧义,比如 results/A_B_C.bam,它怎么分?它会先匹配第一个通配符实际上 Snakemake 使用 正则表达式 解析:{project} 匹配到 A,{sample} 匹配到 B_C?还是 A_B 和 C?
答案是:Snakemake 默认是 贪婪的从右向左匹配。最后一个通配符会尽可能多地吞字符,直到遇到下一个分隔符。所以 A_B_C 会被拆成 project = A_B,sample = C——因为分隔符是 _,而 {sample} 是最后一个通配符,它先从右边开始吞 C,剩下的 A_B 给 {project}。
这很容易出问题,所以我推荐的做法是:
- 使用固定长度的分隔符或数字前缀,比如
{project}_{sample_id:08d}。 - 使用 命名通配符约束,通过
wildcard_constraints限定每个通配符的匹配模式。
wildcard_constraints:
sample = "[A-Za-z0-9]+",
project = "[A-Z]+"
rule example:
output:
"results/{project}_{sample}.txt"
...
这样 ProjectX_Sample1 就会明确匹配:project = ProjectX(符合 [A-Z]+),sample = Sample1(符合 [A-Za-z0-9]+)。如果遇到 ABC_abc123,也能正确解析。
另一种常见场景:同一个通配符多次出现,比如:
rule merge:
input:
"results/{sample}_part1.txt",
"results/{sample}_part2.txt"
output:
"merged/{sample}.txt"
这没问题,通配符会自动保持一致——同一个 {sample} 在所有位置都会替换成相同的值。但如果你不小心写了大小写不同的名字,比如 {Sample} 和 {sample},Snakemake 会当成两个不同的通配符,然后可能因为缺失匹配而报错。
samples 列表,然后用 expand() 生成所有最终目标文件,确保通配符一致性。比如:
SAMPLES = ["HCC1143", "HCC1954"]
rule all:
input:
expand("bams/{sample}.bam", sample=SAMPLES)
这样 Snakemake 会自动为每个样本实例化整个流程。
最后强调一下 多重通配符的冲突避免:
- 不同通配符之间用明显的分隔符(
_、-、.),别只用数字或字母连写。 - 尽量让通配符数量少,能合并的就合并。比如
{project}_{sample}_{lane}可以改成{run_id},然后在 params 里解析。 - 使用
wildcard_constraints给每个通配符“带上镣铐”。
来看一个完整的例子,把上面知识点串起来:
# Snakefile
wildcard_constraints:
sample = "[a-zA-Z0-9]{4,10}",
lane = "L00[1-8]"
rule all:
input:
"bams/merged/HCC1143.bam"
rule fastqc:
input:
"raw/{sample}_{lane}_R1.fastq",
"raw/{sample}_{lane}_R2.fastq"
output:
"qc/{sample}_{lane}_fastqc.zip"
shell:
"fastqc {input} -o qc/"
rule align:
input:
r1 = "raw/{sample}_{lane}_R1.fastq",
r2 = "raw/{sample}_{lane}_R2.fastq"
output:
"bams/{sample}_{lane}.bam"
shell:
"bwa mem ref.fa {input.r1} {input.r2} > {output}"
rule merge_lanes:
input:
bams = expand("bams/{sample}_{lane}.bam", lane=["L001","L002","L003"])
output:
"bams/merged/{sample}.bam"
shell:
"samtools merge {output} {input.bams}"
你看,通配符 {sample} 和 {lane} 在不同规则间自由传递,expand() 动态生成所有 lane 的文件列表,wildcard_constraints 则确保输入文件名符合预期格式。
下面这张图概括了通配符在 Snakemake 中的解析流程:
通配符用得好,你的流程可以轻松适配不同数据集。用不好,你可能会陷入“为什么这里匹配错了”的困惑中。记住:明确分隔、约束先行、测试小提,这三条是我多年积累下来的口诀。希望你们少走我走过的弯路。