规则定义:Snakemake 工作流的骨架
好,从这一章开始,我们正式上手写 Snakemake。前面聊了那么多理念和安装,说实话,不敲代码总觉得心里没底。Snakemake 的核心就是规则(rule)。它就像工作流的骨架,没有规则,一切流程都是空谈。这一节,我们就来仔细看看规则到底怎么写。
规则的基本语法
一个规则最精简的样子,长这样:
rule say_hello:
output:
"hello.txt"
shell:
"echo 'Hello, Snakemake!' > hello.txt"
你看,rule 后面跟着规则的名字,这叫规则名。然后是两个关键字段:output 和 shell。output 告诉 Snakemake 你要生成什么文件,shell 告诉它用什么命令去生成。这个规则叫 say_hello,它的任务就是创建一个叫 hello.txt 的文件,里面写入一句话。
trim_reads、align_bwa,可读性会好很多。
那如果我们想处理多个样本呢?比如有三组数据,每个都跑一遍上面的流程。最原始的办法就是写三个一模一样的规则,把文件名换一下。但这太傻了。你想想看,你的数据可能有几百个,难道要写几百个规则?
所以,Snakemake 引入了通配符。
通配符的引入:让规则活起来
通配符,说白了就是一个占位符。它让一条规则可以匹配多个文件。比如上面的例子,我们改成这样:
rule say_hello:
output:
"results/{sample}.txt"
shell:
"echo 'Hello, {sample}!' > results/{sample}.txt"
这里的 {sample} 就是一个通配符。Snakemake 看到它,会自动把 sample 换成实际的文件名。比如你最终想要 results/A.txt,Snakemake 就知道:哦,sample 就是 A。然后它会把 shell 命令里的 {sample} 也替换成 A。
我曾经犯过一个错:给通配符取名叫 input。结果 Snakemake 直接报错,说这个名字和关键字冲突了。嗯,当时查了半天……所以记住,通配符的名字不要用 input、output、params 这些关键字。
input 和 output 的多种形式
你可能会问:我的规则只需要输入文件时怎么办?或者一个规则需要多个输入、多个输出时又怎么办?别急,Snakemake 都考虑到了。
我们先看最基础的情况:只有一个输入和一个输出。
rule copy_file:
input:
"data/source.txt"
output:
"data/dest.txt"
shell:
"cp {input} {output}"
在 shell 里,你可以直接用 {input} 和 {output} 来引用它们。很简单,对吧?
接下来,多个输入文件怎么办?用列表:
rule merge_files:
input:
"data/part1.txt",
"data/part2.txt",
"data/part3.txt"
output:
"data/merged.txt"
shell:
"cat {input} > {output}"
多个输出也一样:
rule split_file:
input:
"data/all.txt"
output:
out1="data/half1.txt",
out2="data/half2.txt"
shell:
"head -n 50 {input} > {output.out1} && tail -n +51 {input} > {output.out2}"
你看,这里我给输出文件起了名字:out1 和 out2。这样在 shell 里就能用 {output.out1} 来引用具体文件,而不是无差别地引用整个 {output}。这在输出文件类型不同时特别有用。
那输入文件也能命名吗?当然可以:
rule align_reads:
input:
ref="genome.fa",
reads="sample.fastq"
output:
"sample.sam"
shell:
"bwa mem {input.ref} {input.reads} > {output}"
这样做的好处是,命令里的文件引用更清晰了。尤其当输入文件有多个、类型还不一样时,命名能帮你避免搞混。
{input.ref}。我见过有人写成了 {input.ref} 但少了一个点,结果 shell 执行时直接报错。细节决定成败啊。
通配符与多输入的一致性
有了通配符,再用多个输入时会遇到一个新问题:通配符必须跨文件保持一致。比如:
rule count_kmers:
input:
fq="data/{sample}.fastq",
gtf="data/{sample}.gtf"
output:
"results/{sample}_kmer_counts.txt"
shell:
"some_tool {input.fq} {input.gtf} > {output}"
这里,两个输入文件都用了同一个通配符 {sample}。这就意味着,如果你想要 results/sampleA_kmer_counts.txt,Snakemake 会去找 data/sampleA.fastq 和 data/sampleA.gtf。如果这两个文件里有一个不存在,它会直接报错“Missing input files”。
我记得有次做芯片数据处理,我一个规则里同时用了组织名和样本名两个通配符,结果搞混了,一直报错。最后发现是两个通配符顺序写反了。所以,多个通配符时,一定要仔细检查——它们之间的对应关系必须严格一致。
一张图看懂规则结构
这一节的知识点比较多。我画了一张图,把规则的结构、输入输出的关系、通配符的作用串起来。你看完这张图,心里就有底了。
图里很清楚了:规则就像一个函数,输入和输出用通配符串联起来。Snakemake 根据目标文件,自动反推出需要哪些输入,然后一条条执行。
| 关键词 | 功能 | 示例 |
|---|---|---|
rule |
定义一个规则 | rule my_task: |
input |
指定输入文件 | input: "data/{sample}.txt" |
output |
指定输出文件 | output: "results/{sample}.out" |
shell |
执行 shell 命令 | shell: "tool {input} > {output}" |
{wildcard} |
通配符,匹配文件名字段 | ref="genome.{ext}" |
实际案例:从单一到通用
我们来看一个稍微完整一点的案例。假设有多个样本的 .fastq 文件,你想对每个样本做质量控制和比对。先写一个僵硬的版本:
rule fastqc_sampleA:
input:
"data/sampleA.fastq"
output:
"results/sampleA_fastqc.html"
shell:
"fastqc -o results/ {input}"
问题是,如果我有 100 个样本……不不不,谁也不会这么写。用通配符改一下:
rule fastqc_sample:
input:
"data/{sample}.fastq"
output:
"results/{sample}_fastqc.html"
shell:
"fastqc -o results/ {input}"
你看,改动只加了一个 {sample},但规则就从“处理一个样本”升级成了“处理所有样本”。这就是 Snakemake 的魔力。你不需要告诉它样本列表,它自己会从文件系统里推导出来。
不过,这里有个小坑。如果你的 fastqc 命令输出文件名和 Snakemake 预测的不一致,流程会报错。比如 Snakemake 认为输出是 sampleA_fastqc.html,但 fastqc 生成了 sampleA_fastqc.zip。这时,Snakemake 会说“找不到输出文件”,因为你承诺的要生成的东西没出现。所以,规则里的 output 必须和实际生成的文件名一模一样。