第3章:Snakefile基础——规则、输入输出、通配符与shell命令
好了,终于到了能让你真正跑起来的一章。前面两章我们把批次效应的概念和Snakemake的安装环境都捋了一遍,现在该写点正经东西了。
Snakefile,说白了就是一个带“规则”的Python脚本。它不像普通Python那样从上到下顺序执行,而是告诉你:我要从A文件生成B文件,你看着办。
我个人习惯把Snakefile理解成一个“菜谱集合”。每个规则就是一道菜的做法:你需要什么原料(输入),最后端上什么菜(输出),中间怎么操作(shell命令或run代码块)。
3.1 规则的基础结构
一个最简单的Snakefile长这样:
rule hello:
output:
"hello.txt"
shell:
"echo 'Hello, Snakemake!' > hello.txt"
运行一下试试看:
snakemake -c1 hello.txt
为什么会多写一个目标文件?这就是Snakemake的核心理念——一切以文件为中心。你告诉它「我要生成hello.txt」,它就去找到名为hello的规则,执行里面的shell命令。如果hello.txt已经存在且没有修改过,它甚至不会执行。
我在项目中遇到过不少新手直接在shell里写一串命令,然后问我为什么没跑通。其实多半是忘了指定目标文件。
rule all:,里面只放output,不干活。
3.2 输入与输出文件
单个文件好理解,但现实工作流哪有这么简单?你从测序仪拿到的fastq文件,经过质控、比对、定量,每一步都要处理好几个文件。来看看带输入输出的规则怎么写:
rule fastqc:
input:
"sample_1.fastq"
output:
"sample_1_fastqc.html",
"sample_1_fastqc.zip"
shell:
"fastqc sample_1.fastq"
这里有一个细节:input和output可以写多个文件,用逗号分隔。Snakemake会自动判断依赖关系——如果sample_1.fastq不存在或者更新了,它会重新跑fastqc。
这种文件级别的增量更新,说白了就是让计算机替你记住「哪些事做过了,哪些事还没做」。你在命令行里跑pipeline时最容易犯的错就是:某一步失败了,重新跑时又把前面几小时的工作覆盖了。在Snakemake里,这种情况几乎不会发生。
嗯,这里要注意:output文件一旦生成,Snakemake就不会再执行这个规则,除非你主动用snakemake --force强制重跑,或者input文件变化了。
3.3 通配符——让规则活起来
刚才的规则只能处理sample_1.fastq,你有100个样本就要写100条规则?开什么玩笑。通配符就是用来解决这个问题的:
rule fastqc:
input:
"{sample}.fastq"
output:
"{sample}_fastqc.html",
"{sample}_fastqc.zip"
shell:
"fastqc {input}"
看到{sample}了吗?这就是通配符。当你执行:
snakemake -c1 sample_42_fastqc.html
Snakemake会把{sample}自动替换成sample_42,然后去找sample_42.fastq。是不是很优雅?
通配符的值由你指定的目标文件名决定。Snakemake会反向推导:目标文件名里有什么通配符,就从output的模板里提取出来,然后填入input。
我曾经在做一个RNA-seq分析流程时,用了三个通配符嵌套:{condition}_{replicate}_{lane}。一开始觉得挺酷,后来发现debug时脑子要转好几个弯。所以我的建议是:通配符别超过2个,能用一个解决的问题别用两个。
| 元素 | 语法 | 示例 |
|---|---|---|
| 单文件输出 | output: "结果.txt" |
直接写文件名 |
| 多文件输出 | output: "a.txt", "b.txt" |
用逗号分隔 |
| 通配符 | "{wildcard}.txt" |
花括号包裹变量名 |
| 引用输入 | {input} |
在shell里使用通配符展开 |
3.4 shell命令的正确写法
shell命令是Snakefile里最直接的部分——你原来在终端怎么敲,现在就怎么写。但有几个坑你必须知道。
第一,别直接用文件名。
# 错误写法
rule bad:
input: "data.csv"
output: "result.csv"
shell: "python script.py data.csv result.csv"
# 正确写法
rule good:
input: "data.csv"
output: "result.csv"
shell: "python script.py {input} {output}"
用{input}和{output}代替硬编码路径。这样Snakemake才能正确追踪文件依赖,通配符也能自动展开。
第二,小心引号嵌套。
shell命令本身就在双引号里,如果你要执行带单引号的命令怎么办?比如:
rule grep:
input: "bigfile.txt"
output: "filtered.txt"
shell: "grep 'pattern with spaces' {input} > {output}"
这没问题,单引号在双引号内部是合法的。但如果你非要用双引号套双引号,记得用转义:
shell: "grep \"pattern with quotes\" {input} > {output}"
我个人更喜欢用单引号套双引号,或者反过来,眼不花。
第三,管线命令要加set -o pipefail。
默认情况下,Shell管道的返回码是最后一个命令的返回码。如果你的grep失败了但wc -l成功了,Snakemake会认为命令成功——这可能导致下游分析用了错误的数据。解决办法:
shell: "set -o pipefail; cut -f1 {input} | grep 'gene' | wc -l > {output}"
或者用Snakemake的shell语法里的pipefail选项,但我发现直接写更靠谱。
pipefail,结果某次数据量太大导致grep内存溢出,但后面wc -l写了个0到结果文件里。下游分析全用的这个0,排查了整整两天才发现问题。从那以后,凡是管线命令我必加set -o pipefail。
3.5 实战:写一个简单的可变样本规则
把前面学的串起来,写一个能处理多个样本的规则:
rule all:
input:
"sample_A_qc.html",
"sample_B_qc.html",
"sample_C_qc.html"
rule run_fastqc:
input:
"{sample}.fastq"
output:
"{sample}_qc.html"
shell:
"fastqc -o {output} {input}"
执行一下:
snakemake -c1
Snakemake看到rule all需要三个html文件,就去匹配run_fastqc规则的output模板。它会自动把sample_A、sample_B、sample_C三个通配符实例化,检查输入文件是否存在,然后并行或串行地执行。
你可以试试把sample_B.fastq删掉,再跑一次——Snakemake会报错说缺输入文件,但sample_A和sample_C的规则继续执行。这才是工作流引擎该有的样子。
好了,Snakefile的核心要素就这些。接下来你能写出足以处理真实批次效应检测的pipeline骨架了。记住:规则就是你定义的一个个工序,通配符让工序可复用,输入输出让流程自动衔接,shell命令就是你在终端里每天都在敲的东西——只不过Snakemake帮你管好了「哪些需要重跑」「哪些可以跳过」这些烦心事。