工作流基本概念:规则、输入、输出与运行命令
终于要动手写Snakemake了。别急,我们先搞懂四个最核心的概念——规则、输入、输出和运行命令。说白了,Snakemake就是一个聪明的流水线管理员,你告诉它“我想要什么”,它自己琢磨“需要什么原料”和“怎么生产”。
规则(rule):工作流的最小单元
规则是Snakemake的积木块。每个规则定义了一个步骤:拿什么东西,做什么处理,产出什么结果。我记得第一次写Snakemake时,把整个质控流程写在一个规则里,结果调试起来特别痛苦。后来才明白——一个规则只做一件事,这是铁律。
来看一个最简单的规则结构:
rule fastqc_raw:
input:
"data/sample1.fastq"
output:
"results/fastqc_raw/sample1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc_raw"
这个规则叫fastqc_raw。它要吃一个FASTQ文件,然后吐出一个QC报告。Shell部分就是实际要敲的命令。{input}和{output}是Snakemake留好的变量,自动替换成你定义的文件名。
命名建议:规则名要有意义。我习惯用“处理环节+样本标识”的命名方式,比如trim_adapters、align_bwa。项目大了你就知道好处了——报错时一眼能定位是哪一步出了问题。
规则之间靠输入输出串联。你可以把B规则需要的输入,写成A规则的输出。Snakemake会自动梳理依赖关系。嗯,这就是工作流自动化的精髓。
输入(input):明确你要喂什么
input告诉Snakemake:“嘿,运行这个规则前,先把这些文件准备好。”它可以是单个文件,也可以是多个:
rule multi_input_demo:
input:
read1 = "data/sample1_R1.fastq",
read2 = "data/sample1_R2.fastq",
adapter = "config/adapters.fa"
output:
"results/trimmed/sample1_trimmed.fastq"
shell:
"cutadapt -a {input.adapter} "
"-o {output} {input.read1} {input.read2}"
看到没?我给输入文件起了名字(read1、read2、adapter),然后在shell里用{input.名字}引用。这样代码可读性高得多。我在项目中见过有人把所有输入堆在一起,后来自己都分不清哪个是哪个——千万别学。
核心原则:输入文件必须存在,或者能被其他规则生成。Snakemake启动时会检查这条链,缺一个就报错。这其实是个好事——能帮你提前发现数据准备的问题。
输出(output):工作流的目标
output定义了规则要产出的文件。Snakemake用输出文件来判断“这个规则需要运行吗”。如果输出文件已经存在,且比输入文件新,它就跳过——这叫增量构建。
rule skip_if_exists_demo:
input:
"data/sample1.fastq"
output:
"results/fastqc_raw/sample1_fastqc.html"
shell:
"fastqc {input} -o results/fastqc_raw"
第二次运行时,如果sample1_fastqc.html还在,Snakemake直接跳过。这在质控场景里特别实用——你改了一个样本的数据,只重新跑跟它相关的规则,其他不动。
注意陷阱:输出文件不要手动删改。Snakemake依赖文件的修改时间戳做判断。我曾经手贱用touch更新了一个输出文件的时间戳,结果整个下游流程全被触发了,浪费了一个多小时。嗯,血的教训。
运行命令(shell/run/script):真正干活的
Snakemake提供三种方式写命令体:
| 方式 | 适用场景 | 示例 |
|---|---|---|
| shell | 直接执行Shell命令或调用外部工具 | fastqc、cutadapt、bwa mem |
| run | 执行内嵌Python代码 | 数据处理、格式转换、统计计算 |
| script | 运行独立的R/Python脚本文件 | 复杂的统计分析、绘图、报告生成 |
看个run的实例:
rule count_reads:
input:
"data/sample1.fastq"
output:
"results/counts/sample1_count.txt"
run:
total = 0
with open(input[0]) as f:
for line in f:
if line.startswith('@'):
total += 1
with open(output[0], 'w') as out:
out.write(f"Total reads: {total//4}\n")
script方式我最常用,尤其是质控报告那一步:
rule generate_qc_report:
input:
fastq = "data/sample1.fastq",
json = "results/multiqc/sample1_data.json"
output:
"results/reports/sample1_qc.html"
script:
"scripts/report_qc.R"
我的建议:能用shell就别用run,能用run就别用script。为什么?shell最直接,不引入额外依赖。只有遇到复杂的逻辑处理时,再考虑Python或R脚本。这是我经历过几个大项目后总结出来的——脚本太多,环境配置就成了新的噩梦。
画一张图,看明白四个概念的关系
下面这张图总结了规则、输入、输出和运行命令是怎么配合的。Snakemake的哲学就是:定义清楚“我要什么”和“我有什么”,中间的逻辑它自己搞定。
再次强调:Snakemake是一个按需驱动的调度器。你给它一个最终目标,它自己倒推出所有需要跑的规则。中间哪个文件已经有了、不需要重新跑,它也门儿清。这就是自动化批次质控的基础——不用你操心,它自己搞定依赖和增量更新。
实战中的避坑经验
聊几个我实际踩过的坑,帮你省点时间:
- 目录要提前建好——Snakemake不会自动创建输出目录。我习惯在每个规则的shell里顺手加一句
mkdir -p $(dirname {output}),或者在Snakefile开头统一建目录。 - input和output路径最好用绝对路径或相对项目根目录的路径——用
os.chdir切换工作目录时相对路径会乱。我在一次跨服务器迁移时吃过亏,当时路径全乱了。 - 一个规则不要产生多个同名文件——Snakemake靠output的唯一性来映射依赖。重名输出会导致不可预料的跳过行为。
- shell命令里的引号要当心——Snakemake用Python解析shell字符串,单引号嵌套容易出问题。我建议shell命令里统一用双引号。
规则、输入、输出、运行命令——这四个概念构成了Snakemake的全部骨架。接下来几章,我们会把这些积木搭成真正的质控流水线。到时候你会发现,理解了这四个东西,剩下的都是锦上添花。