工作流基础:规则定义、输入输出文件、运行工作流

一个可复现的工作流,核心就是「规则」。你要是理解了规则怎么写,Snakemake 就学会了一半。我自己刚开始接触时,总觉得语法麻烦,后来才发现——说白了,规则就是告诉 Snakemake:你要什么,我有什么,用什么工具来干。

1. 规则的骨架

每个规则由 rule 关键字开头,后面跟一个名字。名字随你取,但别用中文,也别带空格。我习惯用工具名+步骤,比如 bwa_mem、samtools_sort,这样一眼能看出它在干嘛。

一个最精简的规则长这样:

rule bwa_mem:
    input:
        "data/sample_R1.fastq",
        "data/sample_R2.fastq",
        "refs/genome.fa"
    output:
        "results/sample.sam"
    shell:
        "bwa mem {input[2]} {input[0]} {input[1]} > {output}"

看到了吗?input 和 output 是必须写的(除非你用 checkpoint 之类的高级功能,那是后话)。shell 里写你要执行的命令行,用花括号引用输入输出文件。顺序?没有严格的顺序要求,但我建议按使用习惯排好,方便别人读你的 Snakefile。

2. 输入输出文件:规则的“护照”

Snakemake 靠输入输出文件来推导依赖关系。你想想看,它怎么知道先跑哪个规则、后跑哪个?就是看输出文件是不是另一个规则的输入文件。这个推导机制,我称之为“文件的护照”——只有匹配上了,工作流才走得通。

特性说明常见写法
单个文件直接写路径字符串"data/sample.fastq"
多个文件用逗号分隔,Snakemake 会自动打包为列表"a.fq", "b.fq"
命名输入用 键 = 值 的形式,shell 里通过 {input.键} 引用reads = "sample.fq", ref = "genome.fa"
通配符输出用 {sample} 等方式动态匹配文件名"results/{sample}.bam"

我遇到过有人把 input 写成了 inputs(多了一个 s),结果 Snakemake 直接报错——语法检查和类型检查都不放过。所以,要注意变量名严格匹配文档。

这里有个小技巧:如果你有大量相同前缀的输入文件,可以用 expand() 函数批量生成:

rule merge_bams:
    input:
        expand("results/{sample}.bam", sample=["A", "B", "C"])
    output:
        "results/merged.bam"
    shell:
        "samtools merge {output} {input}"

3. 运行工作流:蛇的第一次爬行

写好了 Snakefile,怎么跑?在终端里执行 snakemake 命令就行。最简单的用法:

snakemake -j1

-j 代表并行任务数,1 就是一次只跑一个规则。我第一次跑的时候没加 -j,默认把 CPU 全部占满,搞得电脑卡死……嗯,还是老实点,先从 -j1 开始。

如果你想指定生成某个文件,直接把文件名作为参数传给 snakemake:

snakemake results/sample.bam -j1

这样 Snakemake 就会自动分析依赖,找出需要运行的所有规则,执行一条完整的流水线。它不会多干也不偷懒。哇,这种确定性我最喜欢了。

坑:相对路径 – Snakemake 的工作目录默认是 Snakefile 所在的目录。如果你把 Snakefile 放在项目根目录,而数据在子目录里,输入输出路径要相对根目录来写。我见过有人写绝对路径 /home/user/data/xxx,结果换到服务器跑就炸了。建议始终使用相对路径。

4. 规则执行流程的直观理解

我画了一张图,帮你理解 Snakemake 是怎么把规则串起来的。下面这个例子有两个规则:bwa_mem 和 samtools_sort。第一个生成 .sam,第二个把它排成 .bam。

Snakemake 规则依赖与执行流程 input.fastq rule bwa_mem shell: bwa mem sample.sam rule samtools_sort shell: samtools sort sample.bam 自动推导依赖 Snakemake 根据输出-输入匹配 运行方式 snakemake sample.bam -j1 最终执行顺序:bwa_mem → samtools_sort

当你执行 snakemake sample.bam 时,它先找 sample.bam 是不是某个规则的输出。找到了 samtools_sort,发现它需要 sample.sam;接着找 sample.sam 是 bwa_mem 的输出,而 bwa_mem 的输入文件已经存在磁盘上。好,那就先跑 bwa_mem,再跑 samtools_sort。整个过程不需要你手动排序,Snakemake 自动搞定。

5. 实战中的小坑

我曾经 犯过一个错误:在规则 A 里输出 "sample.bam",规则 B 的输入写成了 "sample.bam "(末尾多了一个空格)。当时怎么找都报错说文件不存在,后来用 ls | cat -A 才揪出来。空格和小写大小写在 Snakemake 里就是天壤之别,千万别不信。

还有,output 文件所在的目录如果不存在,Snakemake 会自动创建目录。但它不会替你生成文件本身——文件得靠 shell 或 run 里的命令去创建。所以别以为写个 output 路径就完事了,命令里忘了写重定向,运行完发现 output 文件是空的(或者干脆不存在),Snakemake 就会报错退出。

我的习惯 – 每次写完一个新规则,先用 snakemake -n 做 dry-run,看推导对不对。等确认无误后再 snakemake -j1 跑一次,顺便用 touch 测试一下增量更新。这样能省很多调试时间。

6. 小结

这一章的内容很基础,但非常关键。规则、输入输出文件、运行命令,这三者构成了 Snakemake 工作流的最小闭环。我个人觉得:只要你把输入输出写清楚,Snakemake 像个聪明的管家,它会自己安排干活顺序。后面几章会在这个基础上加进阶玩法,但那之前,先把基础练扎实了。

嗯,留个小练习:试着写一个 Snakefile,包含两个规则——download_data(用 wget 下载一个文件)和 count_lines(统计行数)。然后运行 snakemake count_lines.txt -j1,看看是不是自动推导出先下载后统计。如果跑通了,恭喜你,Snakemake 的入门关已经过了。