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 执行

Hello World: 从脚本到输出 hello.nf 解析 & 编译 任务图 (DAG) 调度 执行器 (local) Hello, Nextflow! 关键点 • process 是“小作坊” • workflow 是“流水线” • run 触发4个阶段

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(跑起来)。剩下的就是不断加输入、加输出、加逻辑。