4. 第一个Nextflow脚本:Hello World示例
好了,理论铺垫得差不多了。咱们直接上手写一个真正的Nextflow脚本。通过这个经典的Hello World,你会理解process、workflow、run这三个最核心的概念。我个人习惯每次学新工具都从Hello World开始——它能让你快速建立信心。
你想想看,学编程语言时第一个例子往往是“Hello, World!”。为什么?因为它在最简单的代码里包含了这门语言的骨架。Nextflow也一样。我们写一个能打印“Hello, World!”的脚本,然后拆开看看它到底是怎么工作的。
4.1 一个最简单的Hello World脚本
先别管RNA-seq,也别管复杂的数据分析。咱们就写一个流程,让每个进程输出一句话。下面就是完整脚本:
#!/usr/bin/env nextflow
nextflow.enable.dsl=2
process sayHello {
output:
stdout
script:
"""
echo 'Hello, World!'
"""
}
workflow {
sayHello()
}
嗯,这里要注意:#!/usr/bin/env nextflow 这行不是必须的,但我建议你加上。为啥?它能让系统知道这是个Nextflow脚本,方便你直接 ./script.nf 运行。
把上面的内容保存为 hello.nf。然后运行:
nextflow run hello.nf
终端会输出类似这样的日志:
N E X T F L O W ~ version 23.10.0
Launching `hello.nf` [focused_poincare] DSL2 - revision: 8c1f5a2f59
executor > local (1)
[8c/1a5b2f] sayHello | 1 of 1 ✔️
Hello, World!
看到了吗?核心就是三行代码、一条命令。但这里面的门道可不少。
重要区分: Nextflow脚本(.nf文件)和普通的Shell脚本完全不同。Shell脚本里的每个命令按顺序执行;Nextflow脚本里的process是独立运行的,可以并行,也可以在不同的计算环境里执行。这个区别非常关键。
4.2 解剖process:最小的执行单元
process 是Nextflow里的基本计算单元。说白了,它就是一个可以独立运行的“黑盒”。这个黑盒接收输入,执行任务,产生输出。
在上面的例子里:
- process 名称:
sayHello– 给这个黑盒起个名字,方便后面调用。 - output 声明:
stdout– 告诉Nextflow,这个进程的标准输出会被捕获。 - script 块: 花括号里包裹的是实际要执行的Shell代码。
你可能觉得这有点像写一个函数。对,就是这么回事。但有个关键区别:在普通编程语言里,函数是在当前进程里执行的;而Nextflow的process可以丢给集群、容器、云平台去跑。这也是它强大之处。
我曾见过一个团队,把几十个分析步骤全部写成独立的process,然后在workflow里串联起来。每个process都像一个乐高积木,拼一拼就搭出一个完整的分析流程。
4.3 process的四大基本要素
一个完整的process通常包含四个部分。我们直接看个更实际的例子:
process fastqc {
input:
path reads
output:
path "${reads.baseName}_fastqc.html"
script:
"""
fastqc -o . $reads
"""
}
| 要素 | 说明 | 本例 |
|---|---|---|
| input | 声明输入什么数据 | path reads – 一个文件路径 |
| output | 声明输出什么数据 | path "...html" – 生成一个HTML报告 |
| script | 实际运行的命令 | 调用fastqc工具 |
| directives | 运行时的配置选项 | 可指定CPU、内存、容器等 |
小贴士: 初期可以先只关注 input、output、script 三个块。directives 在后面的章节再慢慢熟悉。一口气吃不成胖子。
4.4 workflow:把process串起来的主线
workflow块是整个流程的“导演”。它告诉Nextflow:先运行哪个process,再运行哪个process,数据怎么流动。
在Hello World里:
workflow {
sayHello()
}
这就是在说:执行 sayHello 这个process。如果我有多个process要按顺序执行呢?比如先做质量评估,再做比对:
workflow {
fastqc(reads_ch)
star_alignment(fastqc.out.html)
}
这里 fastqc.out.html 表示取 fastqc 这个process的输出结果,传给下一个process。这种“管道”式的数据传递,就是我特别欣赏Nextflow的地方。代码看得很清楚,数据流向一目了然。
我记得以前用纯Shell脚本写流程,数据传递基本上靠文件名约定:input.fastq → output.sam。脚本稍微一长,根本分不清哪个文件是从哪个步骤来的。Nextflow把这个痛点彻底解决了。
4.5 run:执行你的流程
写完脚本怎么跑?用 nextflow run 命令。咱们再深入看看这个命令的结构:
nextflow run <脚本文件或项目地址> [选项]
举个例子:
nextflow run hello.nf -process.executor local
这里 -process.executor local 是告诉Nextflow:“所有process都在本地计算机上跑”。这是最简模式。以后你还可能用 slurm、pbs、awsbatch 等执行器。
常见坑: 我第一次使用时,以为 nextflow run 后面只能写 .nf 文件。后来发现它可以直接从GitHub仓库拉取流程。比如 nextflow run nf-core/rnaseq 就能运行一个完整的RNA-seq分析流程。这种“一键运行”的能力非常强力,但新手建议先练本地文件。
4.6 理解执行过程:从日志到输出
运行Nextflow脚本后,你会看到一堆日志输出。这些日志不是乱码,它们包含了重要信息。
日志里最关键的几行:
[8c/1a5b2f] sayHello | 1 of 1 ✔️
拆开分析:
[8c/1a5b2f]– 这个process运行的工作目录。Nextflow会给每个process分配一个唯一的工作目录,路径在work/8c/1a5b2f...里。sayHello– process的名字。1 of 1– 总共要运行1次,这是第1次。✔️– 表示执行成功。
为什么要单独给每个process分配工作目录?这是可重复性的关键。每个process的所有临时文件、中间结果都存在自己的目录里,互不干扰。就算某个process失败了,你也可以直接进到它的工作目录里看log、查错误。
我曾经卡在一个看似简单的Hello World脚本上半天。后来发现是我本地没有安装某依赖,但日志里只报了一个很奇怪的内存错误。如果我当时知道可以直接看工作目录里的 .command.log 文件,就能立刻发现问题。
4.7 Nextflow的执行模型:一个简单的可视化
为了让你更直观地理解Nextflow的执行过程,我画了个流程图。这个图展示了Hello World脚本在底层是怎么被处理的:从脚本解析开始,到DSL2编译器检查语法,再到每个process被独立调度执行。你仔细看看,会发现每个process都有自己的工作目录(work dir),这就是Nextflow隔离不同任务的方式。
这个图展示了从脚本到最终输出的完整路径:
- 脚本解析: Nextflow读取
hello.nf,先检查DSL2语法。 - workflow定义: 解析出
workflow块中的执行顺序。 - 调度执行: 为每个process分配执行器(这里是local)。
- 工作目录: 每个process都在自己的目录下运行,所有临时文件都存在那里。
- 输出捕获: process的标准输出被收集到
.command.out文件,然后打印到终端。
这个执行模型正是Nextflow“可重复性”的基石。想想看:每个process都在一个干净的、隔离的环境里运行,依赖、输入、输出都被严格控制。结果怎么可能不一致?
实战建议: 运行完Hello World后,去 work 目录下看看。你会看到类似 work/8c/1a5b2f 这样的文件夹。进去查看 .command.sh(实际执行的命令)、.command.out(标准输出)、.command.err(错误输出)。这些文件在调试阶段非常有用。我曾经靠检查 .command.err 发现一个软件版本不对的问题,节省了整整一天。