流程定义与参数

好,咱们接着聊。前面的章节咱们把版本回溯和数据校验的底层逻辑理了一遍。现在该上手写真正的 Nextflow 流程了。说白了,一个 pipeline 的核心就是 process 和参数传递。这个搞不清楚,后面所有校验都是空中楼阁。

process 定义

process 是 Nextflow 里的最小执行单元。你可以把它想象成一个函数——我指的是那种能独立在容器或集群上跑的函数。每个 process 都有自己独立的脚本、输入输出、以及资源需求。

先看个最简单的例子:

process hello {
    script:
    """
    echo "Hello, World!"
    """
}

嗯,这没什么稀奇的。但真正干活的时候,你得声明输入输出来管理数据流。我记得有个同事刚开始用 Nextflow 时,把所有输入都塞在脚本字符串里硬编码,结果一换数据集整个流程就崩了……

正确的做法是这样:

process fastqc_trim {
    input:
    path reads

    output:
    path "*.html"

    script:
    """
    fastqc $reads
    """
}

这里用 path 声明输入是文件,用 output 声明输出文件的模式。Nextflow 会根据这些声明自动管理暂存目录和版本追踪。这其实就是为数据一致性打基础的关键一步——让每个 process 的输入输出都是显式的、可追溯的。

输入输出声明

输入声明的类型比你想的要多。我常用的就几种:

核心输入类型对照表

类型 说明 常用场景
path 文件或目录 FASTQ、BAM、参考基因组等
val 任意值(String、Integer、Boolean) 阈值、样本名、模式开关
env 环境变量 许可证路径、临时目录
stdin 标准输入 管道串联、压缩流

输出声明的灵活性更大。你可以声明多个输出模式,也可以输出元数据。

process bwa_mem {
    input:
    path index_dir
    path read1
    path read2

    output:
    path "*.bam", emit: bam
    path "*.log", emit: log

    script:
    """
    bwa mem $index_dir/genome.fa $read1 $read2 > aligned.sam
    samtools view -bS aligned.sam > aligned.bam 2> alignment.log
    """
}

注意这里的 emit 参数。它给输出通道起了个名字,后续的 process 可以通过这个名字来引用输出。我特别推荐在长 pipeline 里用这个——能让代码可读性翻倍。

我的习惯: 凡是单个 process 输出超过两个文件的,我都会用 emit 给每个输出起名。不然三个月后你自己都分不清哪个通道里装的是什么。

参数传递方式

参数传递是 Nextflow 里最容易出坑的地方。我见过太多人直接把所有参数硬塞在脚本里,结果一跑不同数据就得改代码——这显然不行。

Nextflow 提供了三种标准参数传递方式:

  • params 字典: 最直接的方式,在流程脚本里通过 params.xxx 引用。
  • 配置文件: 把参数集中管理在 nextflow.config 里。
  • 命令行覆盖: 运行时通过 --param_name value 重写参数。

这三者的优先级关系你想想看:命令行参数 > 配置文件 > 脚本中 params 默认值。这是硬规则,不要试图绕过它。

下面是个完整的示例:

// main.nf
params.outdir = "./results"
params.genome = "/ref/hg38.fa"
params.threads = 4

process align {
    input:
    path reads

    output:
    path "${params.outdir}/aligned.bam"

    script:
    """
    bwa mem -t ${params.threads} ${params.genome} $reads > aligned.sam
    """
}

workflow {
    align(Channel.fromPath(params.reads))
}
// nextflow.config
params {
    outdir = "./my_results"
    threads = 8
}

在实际运行时,你可以这样覆盖参数:

nextflow run main.nf --reads "data/*_R1.fastq.gz" --threads 16

这时候 threads 会取命令行传入的 16,outdir 取配置文件里的 ./my_results,而 genome 因为没被覆盖,保留脚本里定义的 /ref/hg38.fa。

我曾经踩过一个坑: 在配置文件和脚本中定义了相同的参数名但类型不一致——比如脚本里是字符串,配置文件里是整数。Nextflow 不会自动做类型转换,结果 pipeline 因为 threads 参数类型不匹配直接报错。从那以后我养成了习惯:所有参数的定义和默认值都写在脚本里,配置文件只用来覆盖;而且永远保持类型一致。

参数传递的版本管理

聊到版本回溯,参数传递方式直接影响到你能否精确重现一次运行。我个人的建议是:把每次运行的所有参数——包括配置文件里没被覆盖的那些——都完整记录到运行日志或 SQLite 数据库里。这样以后查问题,你能确切知道当时用的是哪套参数。

Nextflow 默认会生成 .nextflow.log,里面记录了参数信息。但我们做数据一致性校验,靠那个不够。更靠谱的做法是手动导出参数快照:

nextflow config -flat > params_snapshot.config

这个命令会把所有解析后的参数以扁平格式输出,相当于给当时的运行拍了张“参数身份证”。

下面这张图总结了本章的核心逻辑:


  

  Nextflow 流程定义与参数传递核心逻辑

  
  
  流程定义 (main.nf)

  
  
  process 定义

  
  输入输出声明

  
  
  参数传递方式

  
  params · 配置文件 · 命令行

  
  
  版本回溯基础

  
  
  
  
  
  
  

  
  
  

  
  
  可重现 Pipeline

  
  
  
  
  

  
  
  关键要点
  · param 优先级:命令行 > 配置文件 > 脚本默认值
  · 输入输出使用显式声明,避免硬编码路径
  · 每次运行保留参数快照 (nextflow config -flat)

  
    
      
    
  

回到参数本身。我建议你在项目的 conf 目录下放三套配置:一个基础配置(用于开发)、一个生产配置(用于正式分析)、一个测试配置(用于 CI/CD)。每个配置里都写清楚参数来源和版本信息,这样数据校验的时候能省去大量查日志的时间。

版本回溯的最佳实践: 把配置文件和流程代码一起版本控制。当你在排查某次异常结果时,只看参数没用——你得知道这次运行对应的代码版本是什么。我习惯在每个配置文件的头部用注释写明所用的 Nextflow 版本和流程的 git commit hash。

// nextflow.config - 生产环境配置
// 关联流程: main.nf @ commit a3f2b1c
// Nextflow 版本: 24.04.3
// 最后更新: 2025-01-15

params {
    outdir = "/data/project_X/results"
    genome = "/ref/hg38/hg38.fa"
    threads = 32
    max_memory = '128.GB'
    fastqc_params = '--nogroup'
}

这些信息在版本回溯和数据一致性校验里起的作用你想想看——它们就是整个流程的“元数据”。没有它们,你看到的参数只是孤立的数字和路径。