流程定义与参数
好,咱们接着聊。前面的章节咱们把版本回溯和数据校验的底层逻辑理了一遍。现在该上手写真正的 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
这个命令会把所有解析后的参数以扁平格式输出,相当于给当时的运行拍了张“参数身份证”。
下面这张图总结了本章的核心逻辑:
回到参数本身。我建议你在项目的 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'
}
这些信息在版本回溯和数据一致性校验里起的作用你想想看——它们就是整个流程的“元数据”。没有它们,你看到的参数只是孤立的数字和路径。