Snakemake 规则核心:run、shell、script、wrapper

好,我们直接进入正题。一个 Snakemake 工作流的核心是什么?说白了就是规则(rule)。而每条规则的核心,就是它要执行的指令。

Snakemake 给了我们四种方式:run、shell、script、wrapper。我最初接触的时候,觉得这几种方式差不多,选哪个都行。后来在真实项目里踩了坑,才明白它们的差异和适用场景。

这四种指令,你选对了,能省大量时间。选错了,可能调试到崩溃。

Snakemake 四种执行指令选择逻辑 定义规则 选择指令 run (Python) 小型处理/测试 shell 系统命令/Bash script Python/R脚本 wrapper 复用社区模块 核心:看你的任务类型决定

run:纯 Python 环境

run 指令是最直观的。你在规则里写一段 Python 代码,Snakemake 直接执行。

适用场景:轻量处理、数据验证、日志统计、测试。

rule process_log:
    input: "raw.log"
    output: "processed.log"
    run:
        with open(input[0], 'r') as f_in:
            data = f_in.readlines()
        with open(output[0], 'w') as f_out:
            for line in data:
                if "ERROR" in line:
                    f_out.write(line)
注意: 我见过很多人把大量计算代码写进 run 块。这会导致 Snakefile 变得臃肿,也难以复用。 run 里的代码,本质上是内嵌在 Snakefile 中的。它无法被模块化导出。

我个人习惯是:run 只用于 10 行以下的简单逻辑。超过这个规模,我会考虑用 script。

shell:直接调用系统命令

shell 指令是最灵活、最常用的。它把指令字符串交给 Bash 去执行。这也是我日常工作里用得最多的指令。

rule bwa_alignment:
    input:
        ref = "genome.fa",
        fq1 = "sample_R1.fastq",
        fq2 = "sample_R2.fastq"
    output:
        bam = "aligned.bam"
    shell:
        "bwa mem {input.ref} {input.fq1} {input.fq2} | samtools sort -o {output.bam}"

你看,这里我用了管道,直接把 BWA 的输出交给 samtools。这种组合在 run 里实现起来就很麻烦。但 shell 里,就是一条命令的事儿。

技巧: 在 shell 指令里,你可以直接使用 {input}、{output}、{params} 等通配符。Snakemake 会自动把它们替换成实际路径。记住,input 如果有多个文件,在 shell 里会被展开成空格分隔的字符串。

script:将脚本分离出来

当你的逻辑变得复杂,比如需要几百行的 Python 或 R 代码,塞进 run 块里就很不合适了。这时候,script 指令登场。

它的核心思想是:把脚本放在单独的文件里,规则只负责调用。Snakemake 会自动传递 snakemake 对象给脚本,脚本可以通过 snakemake.input、snakemake.output 访问文件和参数。

rule complex_analysis:
    input: "data.csv"
    output: "results.rds"
    script:
        "scripts/analyze.R"

在 scripts/analyze.R 中:

library(tidyverse)
df <- read.csv(snakemake@input[[1]])
result <- df %>%
    group_by(group) %>%
    summarise(mean = mean(value), sd = sd(value))
saveRDS(result, snakemake@output[[1]])

我曾经在项目里把所有的生物信息学分析逻辑都写在 Snakefile 里,那文件足足有 3000 行。后来重构,把所有核心算法提取到 script 目录里。不仅 Snakefile 变清爽了,脚本还能被其他工作流复用。嗯,这是一个明智的决定。

关键点:script 指令下的脚本,默认会获得全局的 snakemake 对象(Python)或 snakemake S4 对象(R)。脚本可以从这个对象中读取输入、输出、参数、通配符等信息。这意味着你的脚本完全与 Snakemake 上下文解耦,只通过 API 通信。

wrapper:站在巨人的肩膀上

wrapper 是四种指令中概念最简单的,但用好了效率最高。说白了,它就是指向一个 Git 仓库中的预定义工作流模块。

Snakemake 官方和社区维护了一个庞大的 wrapper 仓库:snakemake-wrappers。里面封装了常见的生物信息学工具,比如 fastp、bwa、samtools、picard 等。

rule fastp_trimming:
    input:
        fq1 = "sample_R1.fastq",
        fq2 = "sample_R2.fastq"
    output:
        fq1 = "trimmed_R1.fastq",
        fq2 = "trimmed_R2.fastq",
        html = "fastp_report.html"
    params:
        extra = "-q 20 -u 30"
    wrapper:
        "v3.10.1/bio/fastp"

注意这里的 wrapper 路径。v3.10.1 是版本 tag,bio/fastp 是仓库子目录。Snakemake 会自动下载并执行这个 wrapper。

指令 执行环境 代码复用性 适用场景
run Snakemake 内部 Python 低 简单逻辑、测试、后处理
shell 系统 Shell(Bash) 中 调用命令行工具、管道组合
script 独立脚本(Python/R/Julia 等) 高 复杂分析、模块化设计
wrapper 预定义社区模块 极高 快速集成标准工具
避坑指南: 我曾经在团队里强行推广 wrapper,结果发现有些 wrapper 的版本与内部环境不兼容。务必先检查 wrapper 的依赖和版本号。另外,wrapper 是“黑盒”,你无法轻易修改内部逻辑。如果有定制需求,老老实实用 shell 或 script。

好了,四种方式讲完了。你选哪个?没有绝对的正确答案。我个人的选择顺序是:

  1. 有没有现成的 wrapper? 有就用,能省大量时间。
  2. 没有 wrapper,那逻辑复杂吗? 复杂就抽成 script,保持整洁。
  3. 不复杂,但需要调命令行工具? 用 shell,直接高效。
  4. 只是简单的数据清洗或验证? 用 run,方便快捷。

这个顺序,你可以在实际项目中试一试。我用这套规则写了十几个工作流,几乎没有翻过车。