3、第一个Nextflow脚本:编写简单的Hello World工作流
每个编程语言都有个 Hello World,Nextflow 也不例外。但说实话,我第一次跑通这个脚本的时候,心里还是有点小激动的——它让我看到了「数据流」是怎么在进程之间传递的。这一节我们手写一个最简的工作流,从中拆解 process、workflow 和 run 这三个核心概念。
3.1 最简单的 process 长什么样
在 Nextflow 里,process 是执行任务的最小单元。你可以把它想象成一个「小作坊」:扔进去原材料,它按照配方加工,然后吐出成品。配方就是脚本里的命令或程序。
我们先看一个直接把字符串打印出来的 process:
process sayHello {
output:
stdout
script:
"""
echo 'Hello, Nextflow!'
"""
}
这个 process 没有定义输入,只有输出——把标准输出捕获出来。注意 script 块里用三引号包裹,里面是原样的 shell 命令。我刚开始写的时候老忘记加 stdout 声明,结果跑了半天发现日志是空的……
3.2 workflow 把 process 串起来
workflow 就是「工厂的流水线」。它负责定义 process 谁先谁后,数据怎么传。对于上面的 sayHello,workflow 可以简单到只调用它:
workflow {
sayHello()
}
但如果我想让工作流更灵活一点,比如允许外部传参,那就要用上 params 了:
params.greeting = 'Hello, Nextflow!'
process sayHello {
input:
val greeting
output:
stdout
script:
"""
echo '$greeting'
"""
}
workflow {
sayHello(params.greeting)
}
我这里把字符串作为输入传进了 process。你想想看,如果以后有几百个样品,每个样品对应不同的问候语,你只需要把 params 替换成一份输入列表,工作流瞬间就能批量处理。这就是 Nextflow 最爽的地方——从单样本到大规模并行,结构几乎不用改。
3.3 运行你的第一个 Nextflow 脚本
把上面最终版代码保存为 hello.nf。然后在终端执行:
nextflow run hello.nf
你会看到类似这样的日志:
N E X T F L O W ~ version 23.04.0
Launching `hello.nf` [distracted_fermi] DSL2 - revision: abcdef1234
executor > local (1)
[6c/abcde] process > sayHello (1) [100%] 1 of 1 ✔
Hello, Nextflow!
日志第一行告诉你 Nextflow 版本。第二行是启动信息,每次运行都会生成一个随机代号(比如 distracted_fermi),用于区分不同运行记录——这在排查历史运行问题时特别有用。
第三行显示执行器是 local,意味着任务跑在本机。最后的输出行就是 Hello, Nextflow!。
小技巧: 如果你在 Windows 上跑,注意换行符。我遇到过几次因为脚本里混了 CRLF 导致 process 报错。建议统一用 LF,或者在 nextflow.config 里设置 shell = ['/bin/bash', '-c']。
3.4 理解 run 背后的四个阶段
当你敲下 nextflow run 之后,Nextflow 内部到底做了什么事?我把它拆成四个阶段:
| 阶段 | 做了什么 |
|---|---|
| 解析 | 读取 .nf 脚本,构建抽象语法树(AST),检查语法和 DSL2 结构。 |
| 编译 | 将 process 和 workflow 定义编译成可执行的任务图(DAG)。 |
| 调度 | 根据 DAG 中任务的依赖关系,把任务提交给执行器(本地、集群、云)。 |
| 执行 | 每个 process 在独立的临时工作目录里运行,输出被收集并传递给下游。 |
我个人觉得其中最巧妙的是「调度」阶段。它不会一股脑儿把所有任务都丢出去,而是等上游任务完成才能启动下游任务。如果你的 workflow 里有多个独立分支,它们可以并行跑——完全不用你手动配置线程池。
我见过有人为了加速,在 process 里手动写 parallel,结果把 Nextflow 原生的调度机制搞乱了。嗯,记住一条原则:让 Nextflow 管调度,你只写业务逻辑。
3.5 图解:一次完整的 Hello World 执行
3.6 常见踩坑与最佳实践
- 脚本编码: 我遇到过在 Linux 上写 Windows 换行的脚本,Nextflow 跑的时候报
Command not found。解决办法是用dos2unix转换,或者在编辑器里设置行尾为 LF。 - 空格与制表符: process 里
script块内部的缩进不要用制表符,否则解析会挂。我习惯用 4 个空格,从不出错。 - 输出声明: 如果 process 没有
output块,下游 process 永远拿不到它的数据。虽然工作流不会报错,但会留下一个「幽灵」通道。检查日志时如果发现某个 process 的out是空的,八成是忘了声明输出。 - workflow 命名: 一个脚本只能有一个
workflow块。如果你有多个工作流逻辑,可以用子工作流(sub‐workflow),这在后续章节会展开。
警告: 不要在 process 内部写 nextflow 命令或者 workflow 关键字。process 只是 shell 脚本容器,它不知道外面是 Nextflow 世界。我曾经为了调试,在 process 里顺手敲了 nextflow run,结果递归调用把自己整懵了……
3.7 小结
这一节我们写了第一个 Nextflow 脚本。从 process 的定义,到 workflow 的编排,再到 nextflow run 的执行,整个流程就像搭积木。你只要记住三个词:process(做什么)、workflow(怎么连)、run(跑起来)。剩下的就是不断加输入、加输出、加逻辑。