输入与输出文件:文件通配符与 Wildcards 实战
刚接触 Snakemake 那会儿,我天真地以为每条规则只能一次处理一个文件。
后来我接了个 RNA-seq 项目,四十几个样本,每个样本有双端测序的两个 fastq 文件。如果为每个样本写一条规则……那画面太美我不敢看。
还好,Snakemake 提供了通配符(Wildcards),这玩意儿就是批量工作流的灵魂。少写几十行重复代码,一个规则管所有样本。
到底什么是通配符?
说白了,花括号 {} 就是通配符的标记。你在 input 或 output 的文件路径里写 {sample},Snakemake 就会自动把 sample 当作一个变量,从实际文件路径里提取出具体的值。
举个例子:
rule align:
input:
"data/{sample}_R1.fastq",
"data/{sample}_R2.fastq"
output:
"results/{sample}.bam"
shell:
"bwa mem ref.fa {input[0]} {input[1]} > {output}"
当你让 Snakemake 生成 results/Sample1.bam 时,它会自动把 {sample} 匹配成 Sample1。然后 input 里的 {sample} 也变成 Sample1,所以它去找 data/Sample1_R1.fastq 和 data/Sample1_R2.fastq。
这里有个关键点:通配符**必须**同时出现在 input 和 output 中(至少 output 里要有)。
你想想,output 里写了 {sample},Snakemake 才知道要匹配哪个值;如果 input 里也用了同样的通配符名,它就能把值传进去。如果 input 里没有,那这个通配符就只是 output 里的一个占位符,不算真正意义上的通配符——不过这种用法偶尔也用在生硬的文件名重命名上,我一般不推荐。
通配符的约束:别让它乱匹配
实际项目中,文件名可能长这样:
data/Sample1_L001_R1.fastq
data/Sample1_L002_R1.fastq
data/Sample1_L001_R2.fastq
data/Sample1_L002_R2.fastq
如果你只写 {sample}_L001_R1.fastq,Snakemake 默认通配符会尽量匹配最短的字符串,但它也可能匹配到 Sample1_L001 这种过长的东西——然后就会找不到对应的 input 文件。
这时候就需要 wildcard_constraints 来限定通配符的格式:
rule align:
input:
"data/{sample}_L001_R1.fastq",
"data/{sample}_L001_R2.fastq"
output:
"results/{sample}.bam"
wildcard_constraints:
sample = "[a-zA-Z0-9]+" # 只允许字母和数字
shell:
"bwa mem ref.fa {input[0]} {input[1]} > {output}"
你可以把它理解成正则表达式的小检查。我有个习惯:只要通配符前后还有固定字符(比如 _L001),我都会加约束,避免匹配越界。
多个通配符的协同
一个规则当然可以同时用多个通配符。比如你要按样本和测序 Lane 来拆分输出:
rule trim:
input:
"data/{sample}_{lane}_R1.fastq",
"data/{sample}_{lane}_R2.fastq"
output:
"trimmed/{sample}_{lane}_R1.fastq",
"trimmed/{sample}_{lane}_R2.fastq"
shell:
"cutadapt -j 4 -o {output[0]} -p {output[1]} {input[0]} {input[1]}"
当你请求 trimmed/Sample1_L001_R1.fastq 时,sample 被匹配成 Sample1,lane 被匹配成 L001。Snakemake 自动推导出所有依赖。
这里有一个坑:多个通配符同时出现时,Snakemake 会尝试用**贪婪匹配**,但它会用默认的 [^/]+ 来匹配每个通配符。如果固定字符太少,匹配可能出错。
我曾经遇到过这样一条规则:
output: "results/{sample}_{date}.txt"
文件名是 results/Sample1_20250101.txt,Snakemake 把 sample 匹配成 Sample1,date 匹配成 20250101,没问题。但如果文件名里有下划线,而固定字符又区分不清,就需要用 wildcard_constraints 分别约束:
wildcard_constraints:
sample = "[A-Za-z0-9]+",
date = "\\d{8}"
通配符在 input 中的特殊写法:函数形式
有时候 input 文件不是简单的字符串拼接,而是需要根据样本 ID 去查一个映射表。比如样本名是 SRR123456,但输入文件存在 fastq/SRR123456_1.fastq.gz。
你可以写一个 Python 函数:
def get_fastq(wildcards):
return f"fastq/{wildcards.sample}_1.fastq.gz"
rule mapping:
input:
r1 = get_fastq
output:
"aligned/{sample}.bam"
shell:
"bwa mem ref.fa {input.r1} > {output}"
这里的 wildcards 是一个对象,包含当前规则的所有通配符值。你可以用 wildcards.sample 拿到当前样本名。这种写法在处理样本名称和文件名不一致时特别有用。我经常把它用于样本名到 FTP 路径的转换。
常见错误与避坑指南
我收集了几个初学者最容易摔跤的地方:
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
| MissingInputException | 通配符在 output 里,但 input 里没写同名的通配符 | 检查 input 路径是否包含相同的 {wildcard} |
| WildcardError: no wildcard in output | 你给规则的目标文件是绝对路径,没有通配符 | 确保 output 里至少有一个花括号变量 |
| 匹配到了多余字符 | 固定字符和通配符边界模糊 | 加 wildcard_constraints 或者用 _ 等分隔符固定 |
| 多个通配符解析混乱 | 通配符之间没有足够固定的分隔字符 | 使用 wildcard_constraints 分别限定格式 |
小技巧:调试通配符时,可以在规则里加一行 shell: "echo {wildcards}" 先跑一下,看 Snakemake 到底把通配符解析成了什么值。
注意:通配符名不能包含点号 . 或连字符 -,只能用字母、数字和下划线。我见过有人写 {sample.file},结果抛语法错误。老老实实用下划线分隔,比如 {sample_file}。
通配符的匹配流程(一张图说明)
我觉得用一张图来解释通配符怎么把目标文件拆成 input 最直观。下面是我画的:
核心要点:通配符就是 Snakemake 的「万能钥匙」。你只需定义一次规则、写一次命令,Snakemake 会自动用通配符匹配出所有组合。记住 output 里的通配符是驱动者,input 里的通配符是被动继承者。
总结一下通配符的常用模式
| 场景 | 示例 | 说明 |
|---|---|---|
| 单样本处理 | output: "results/{sample}.bam" | 通配符 {sample} 从目标文件名提取 |
| 双端数据 | output: "results/{sample}_{lane}.bam" | 两个通配符,分别对应样本和Lane |
| 限制匹配范围 | wildcard_constraints: sample = "\\d+" | 只匹配数字样本ID |
| 动态input函数 | input: lambda wildcards: f"path/{wildcards.sample}.txt" | Python Lambda 灵活构造路径 |
你想想看,如果没有通配符,每个样本写一条 rule 是什么感觉?
我当年第一个 Snakemake 工作流写了 200 多行,后来用通配符重写,直接缩到 40 行。嗯,这才是自动化该有的样子。