4. 第一个 Nextflow 脚本:Hello World
好,咱们开始动手了。
很多教程一上来就扔给你一个复杂的管道,搞得人一头雾水。我个人的习惯是,从最简单的“Hello World”开始。你不用理解所有细节,先跑通它,感受一下 Nextflow 的节奏。
说白了,每个 Nextflow 管道就两样东西:process 和 workflow。process 是干活的那个工人,workflow 是指挥工人怎么干活的工头。今天咱们就把它俩搞清楚。
4.1 先看完整的代码
创建一个文件,叫 hello.nf。把下面这段代码贴进去。别急着复制,一行一行看,我待会儿逐句拆解。
#!/usr/bin/env nextflow
nextflow.enable.dsl=2
process sayHello {
output:
path "hello.txt"
script:
"""
echo "Hello, Nextflow!" > hello.txt
"""
}
workflow {
sayHello()
println "Workflow finished successfully."
}
嗯,看起来挺简单的吧?咱们一句一句来。
4.2 拆解 process {} — 管道的工人
process 是 Nextflow 里最核心的组件。一个 process 就是一段可以被独立执行的脚本。你可以把它想象成一个函数,但它不是跑在你的本地电脑上——它可以跑在本地、SGE 集群、SLURM、AWS Batch 等等。
核心要点:每个 process 都有自己的输入、输出和执行脚本。它只关心自己的一亩三分地。
咱们看看上面这个 process 的结构:
- process 名字:
sayHello,就是这个工人的名字。 - output 声明:
path "hello.txt",告诉 Nextflow,这个工人干完活会产出一个叫hello.txt的文件。 - script 块:里面是真正要执行的命令——
echo "Hello, Nextflow!" > hello.txt。
你在项目里可能会遇到各种 process。有的处理序列比对,有的做质控,有的跑模型训练。但它们的骨架都一样——就是这个 output 和 script 结构。
注意:script 块里的内容是用三引号 """ 包裹的。这意味着里面可以写多行命令,也可以用 shell 变量。我第一次用的时候,忘了加三引号,结果 Nextflow 把整个脚本当成了一行字符串,报错报得我头皮发麻。
4.3 拆解 workflow {} — 管道的管理者
workflow 块是管道的入口。它定义了 process 的执行顺序和数据传递关系。
workflow {
sayHello()
println "Workflow finished successfully."
}
这里只有两行:
sayHello():调用我们上面定义的 process。注意,在 Nextflow DSL2 里,process 被当作一个函数调用。println:打印一句话,告诉你管道跑完了。
workflow 块里可以串联多个 process,可以写条件判断,可以循环。但现在,我们就只调用一个 process,让流程跑起来。
一个小技巧:你可以在 workflow 里用 println 打印调试信息。我跑管道的时候,经常用它来标记每一步开始和结束。比如:println "Step 1: Alignment started"。
4.4 运行脚本
现在,打开终端,来到 hello.nf 所在的目录。执行:
nextflow run hello.nf
你会看到类似这样的输出:
N E X T F L O W ~ version 24.04.0
Launching `hello.nf` [sleepy_curie] DSL2 - revision: 1
executor > local (1)
[5a/3b7c4d] process > sayHello (1) [100%] 1 of 1
Workflow finished successfully.
看到最后一行 "Workflow finished successfully." 了吧?这就跑成了。你检查一下当前目录,会多出一个叫 hello.txt 的文件。打开它:
cat hello.txt
输出:
Hello, Nextflow!
完美。你的第一个 Nextflow 管道跑通了。
注意:每次运行,Nextflow 会在 work/ 目录下创建一堆临时文件夹。这些是每个 process 的工作目录,里面有你的脚本、输入输出和日志文件。我建议你偶尔看一下 work/ 目录,理解一下 Nextflow 是怎么管理中间过程的。
4.5 再看一个更真实的例子
上面那个太简单了?咱们加一点点实际的感觉。假设我们要批量处理两个文件。这里用到了 process 的 input 声明。
#!/usr/bin/env nextflow
nextflow.enable.dsl=2
process formatMessage {
input:
val message
output:
path "${message}.txt"
script:
"""
echo "Message received: ${message}" > ${message}.txt
"""
}
workflow {
greetings = ["Hello", "Bonjour", "Hola"]
for (greeting in greetings) {
formatMessage(greeting)
}
println "All messages formatted."
}
这个例子跟上面有什么不同?
- 输入参数:
input: val message声明 process 接收一个值(不是文件),叫message。 - 输出文件名动态生成:
path "${message}.txt",比如传入 "Hello",就生成Hello.txt。 - workflow 里用了循环:
for (greeting in greetings)依次调用 process。
运行它:
nextflow run format.nf
你会看到三个 process 并行跑了。每个 process 都生成一个自己的输出文件。
记得:Nextflow 默认会并行执行没有依赖关系的 process。所以你不需要自己写多线程代码。我以前做批量质控时,直接用了 for 循环调用质量检查 process,Nextflow 自动帮我利用所有 CPU 核——挺爽的。
4.6 知识结构图
下面这张图总结了本章讲的核心内容:process 和 workflow 的关系。
4.7 小结:两个关键点
今天咱们的核心就两个东西:
- Process:管道的执行单元。它定义了输入、输出和脚本。你只需要写好要跑的命令,Nextflow 帮你管理文件、环境和工作目录。
- Workflow:管道的编排单元。它决定 process 的调用顺序和数据流。你可以写循环、条件判断,甚至嵌套 workflow。
这两个概念会贯穿整个课程。后面所有内容——并行任务、数据通道、可复用模块、错误重试——都是围绕 process 和 workflow 展开的。
避坑指南:我曾经在写第一个管道时,把 process 和 workflow 写在了同一个文件里,然后搞混了它们的命名空间。记住:每个 .nf 文件可以有多个 process,但只能有一个 workflow。如果你写了两个 workflow,Nextflow 会报错重复定义。
现在,你可以在终端里运行 nextflow run hello.nf,看看屏幕上的日志,再看看生成的 hello.txt。