3. Snakefile 基本结构:规则、输入输出、Shell 指令

Snakefile 说白了就是工作流的“剧本”。

我刚接触 Snakemake 时,以为它就是 Makefile 的 Python 亲戚,结果发现远比那灵活。今天我把最核心的三个部分——规则定义、输入输出、Shell 指令——拆开揉碎了讲清楚。

3.1 规则:工作流的最小单元

每个规则就像生产线上的一个工位。我习惯把规则想象成“一个文件到另一个文件的转换过程”。

基本结构长这样:

rule 规则名称:
    input:
        "输入文件路径"
    output:
        "输出文件路径"
    shell:
        "执行的命令"

为什么要有规则名?

  • 方便调试时定位
  • 可以被其他规则显式调用
  • 日志输出时能看到是哪个规则在执行
命名建议:用下划线分隔的动词+名词组合,比如 trim_fastq、align_bam。别用中文或特殊字符。

3.2 输入输出:定义依赖关系

Snakemake 最聪明的地方在于——它通过输入输出自动识别依赖关系。

你想想看,如果规则 A 的输出是规则 B 的输入,那 A 必须在 B 之前运行。Snakemake 会自动排序。

举个例子:

rule download_data:
    output:
        "raw_data/sample1.fastq.gz"
    shell:
        "wget -O {output} https://example.com/sample1.fastq.gz"

rule trim_reads:
    input:
        "raw_data/sample1.fastq.gz"
    output:
        "trimmed/sample1_trimmed.fastq.gz"
    shell:
        "fastp -i {input} -o {output}"

trim_reads 需要 download_data 的输出,Snakemake 就知道先下载,再裁剪。

路径陷阱:我曾经因为路径写错,浪费了一整天。input 和 output 里尽量用绝对路径,或者确保工作目录正确。

3.3 Shell 指令:真正干活的命令

Shell 指令就是规则要执行的真正命令。

你可以写 bash 命令、Python 脚本、甚至是 R 代码——只要能在终端运行。

关键语法:

  • {input}:自动替换成 input 里的文件路径
  • {output}:自动替换成 output 里的文件路径
  • 多个输入输出时用索引:{input[0]}、{output[1]}

看这个实际案例:

rule fastqc_trimmed:
    input:
        "trimmed/sample1_trimmed.fastq.gz"
    output:
        "qc/trimmed/sample1_fastqc.html",
        "qc/trimmed/sample1_fastqc.zip"
    shell:
        """
        fastqc {input} -o qc/trimmed/
        """
多命令技巧:Shell 里可以写多行命令,用 && 连接,或者用 Python 的 run: 指令代替。

3.4 实战案例:一个完整的质控流程

把上面三个部分组合起来,就是一个完整的 Snakemake 工作流。

下面是我在项目中用过的质控流程骨架:

rule get_fastq:
    output:
        "data/sample.fastq.gz"
    shell:
        """
        wget -O {output} ftp://example.com/sample.fastq.gz
        """

rule raw_qc:
    input:
        "data/sample.fastq.gz"
    output:
        "qc/raw/sample_fastqc.html"
    shell:
        """
        fastqc {input} -o qc/raw/
        """

rule detect_adapters:
    input:
        "data/sample.fastq.gz"
    output:
        "reports/adapters.txt"
    shell:
        """
        cutadapt --detect-adapter-for-pe -o /dev/null {input} 2> {output}
        """

rule trim:
    input:
        fastq="data/sample.fastq.gz",
        adapter="reports/adapters.txt"
    output:
        "trimmed/sample.fastq.gz"
    shell:
        """
        cutadapt -a file:{input.adapter} -o {output} {input.fastq}
        """

rule trimmed_qc:
    input:
        "trimmed/sample.fastq.gz"
    output:
        "qc/trimmed/sample_fastqc.html"
    shell:
        """
        fastqc {input} -o qc/trimmed/
        """

这个流程的依赖关系很清楚:

  1. 先下载原始数据
  2. 对原始数据做 FastQC
  3. 检测接头序列
  4. 根据检测结果做裁剪
  5. 对裁剪后的数据再做一次质控
用 input: 命名参数:上面 trim 规则里,我用了 input.fastq 和 input.adapter,这样在 shell 里引用时更清楚。这是我在实践中养成的习惯。

3.5 知识结构图

下面这张图把 Snakefile 的核心结构画了出来,方便你对照记忆:

Snakefile 核心结构 Snakefile 规则 (rule) 输入/输出 Shell 指令 唯一名称 可被其他规则依赖 文件路径 自动解析依赖关系 {input} {output} 占位符 多行命令用 """ 包裹 依赖图 → 自动排序执行 实际执行顺序示例 raw_qc detect_adapters trim

3.6 避坑指南

说几个我踩过的坑:

  • 输入输出不匹配:output 写了两个文件,但 shell 只生成一个,Snakemake 会报错。
  • 路径歧义:同一个文件名出现在不同目录,Snakemake 会傻眼。要用 wildcard 区分。
  • 命令太长:Shell 指令超过两行时,记得用 """ 包裹,不然容易断句出错。
我曾经因为忘记在 shell 命令里加 {output},结果文件死活生成不了。Snakemake 检查的是 output 字段里的文件是否存在,不是看你命令里生成了什么。

3.7 表格总结:不同场景下的规则写法

场景 输入 输出 Shell 示例
单文件 单个文件 单个文件 fastq {input} -o {output}
多输入 多个文件 单个文件 cat {input[0]} {input[1]} > {output}
多输出 单个文件 多个文件 split -l 1000 {input} {output[0]}
无输入 无 单个文件 echo "hello" > {output}
无输出 单个文件 无 rm {input}