3. 第一个 Nextflow 脚本:Hello World

学任何新东西,我都喜欢先从“跑起来”开始。

Nextflow 也不例外。你不需要把 DSL2 的语法全背下来,先让一个最简单的 workflow 跑通,感受一下数据流是怎么走的,比什么都重要。

这一节,我们来写一个“Hello World”。它很简单,但足以帮你理解 Nextflow 脚本的基本骨架。

3.1 先写一个最小 workflow

新建个文件,就叫 hello.nf。我习惯把每个章节的练习脚本都按章节命名,方便回头找。

对着下面这个代码,直接抄进去:

#!/usr/bin/env nextflow

nextflow.enable.dsl=2

params.greeting = "Hello"

process sayHello {
    publishDir "./results", mode: 'copy'

    input:
    val x

    output:
    path "hello.txt"

    script:
    """
    echo "${x}, World!" > hello.txt
    """
}

workflow {
    sayHello(params.greeting)
    sayHello.out.view { "写到文件中的内容是: $it" }
}

保存后,在终端里跑一条命令:

nextflow run hello.nf

如果你看到类似这样的输出,那就成了:

N E X T F L O W  ~  version 23.10.0
Launching `hello.nf` [golden_gauss] DSL2 - revision: abc1234
executor >  local (1)
[6d/9e8f4c] process > sayHello (1) [100%] 1 of 1
写到文件中的内容是: Hello, World!

终端上打印了一行,工作目录里也会生成一个 hello.txt,里面写着 Hello, World!。

嗯,这就是你的第一个 Nextflow 脚本。

💡 运行提示
如果报错说找不到 nextflow 命令,先去装一下。Nextflow 需要 Java 8 或更高版本,装好后 nextflow -version 能正常显示就说明环境没问题。

3.2 脚本结构:三大部分,缺一不可

刚才那段代码看着不长,但 Nextflow DSL2 脚本的核心部件都在里面了。我拆开来跟你讲。

我们来看一个骨架图,先有个整体感觉:

Nextflow DSL2 脚本结构 main.nf #!/usr/bin/env nextflow nextflow.enable.dsl=2 params.greeting = "Hello" process sayHello 定义计算单元:入参、出参、执行脚本 它封装成独立的目录和任务来运行 workflow { 组织 process 执行顺序与数据流动 sayHello(params.greeting) sayHello.out.view { ... } }

说白了,一份完整的 DSL2 脚本就是三个部分拼在一起。

组成部分作用你写的时候要注意什么
头部声明指定解释器、启用 DSL2 模式第一行是 shebang,第二行必须写 nextflow.enable.dsl=2,少了这一行,DSL2 语法全不认。
process 定义封装单个计算步骤每个 process 就像一个黑盒:接收输入、产生输出、跑一段脚本。内部的脚本在独立工作目录里执行。
workflow 块编排 process 的调用和数据流向DSL2 里所有流程逻辑都写在 workflow {} 里。process 在这里被“调用”而非直接写在全局。
📌 我一个老习惯
头部声明里 nextflow.enable.dsl=2 一定要放在 params 定义的前面。顺序错了,Nextflow 会直接报语法错误。

3.3 理解代码的运行机制

我们来过一遍执行流程,你对照着上面的代码看。

  1. 脚本启动:Nextflow 读到 hello.nf,先看头部声明,确认是 DSL2 模式。
  2. 参数解析:读到 params.greeting = "Hello",把默认值存好。你可以在运行命令时用 --greeting Hi 覆盖它。
  3. 编译 process:扫到 process sayHello 块,但不立即执行——只是注册这个步骤。
  4. 进入 workflow:执行 workflow {} 内部逻辑。先调用 sayHello(params.greeting),把参数传进去。
  5. process 执行:Nextflow 为 sayHello 分配一个唯一的工作目录(比如 work/6d/9e8f4c.../),在里面运行 echo "Hello, World!" > hello.txt。
  6. 输出通道:process 完成后,hello.txt 被发布到 ./results 目录。同时,sayHello.out 通道自动推送文件路径。
  7. 查看结果:sayHello.out.view { "写到文件中的内容是: $it" } 从通道中取出文件路径,用 $it 拼接成字符串打印到终端。

你想想看,这个模式其实很通用。你改改 script 里的命令,换个参数,就能处理不同的任务了。

3.4 print 和 view 的区别

脚本里我用了 view,没有用 print。这两个都是输出用的,但场景完全不同。

操作符作用对象典型用法
view通道(channel)取出通道中的每个元素,打印出来
print / println普通变量在 workflow 块或 process 的 script: 内用

view 是 Nextflow 的通道操作符。它会监听通道里的每一个值,当新值进入时就执行你给的闭包。我很多项目调试时,会在关键步骤后面加一个 .view 看看数据流是否正常。

print 其实就是 Groovy 原生的 System.out.print,在 workflow 块里也能用,但它不能直接“监听”通道。举个例子:

workflow {
    sayHello(params.greeting)
    
    // 用 view 看通道内容
    sayHello.out.view { "view 看到: $it" }
    
    // 用 println 打印普通字符串
    println "workflow 已经结束"
}
⚠️ 我踩过的坑
有一次我想在 workflow 里直接 print sayHello.out,结果打印出一串看不懂的对象地址。后来才意识到,通道不是普通变量,它的内容需要 view 才能看到。

记住:通道里的数据流,要用通道操作符去访问。

3.5 完整示例:调整一下试试

学到这里,我建议你动手改几个地方,跑跑看:

  1. 把 params.greeting 改成自己的名字,比如 "Hi"。
  2. 把 hello.txt 改成 greeting.txt,同时更新 output 块中的路径。
  3. 在 script 里多写一行 date >> greeting.txt,看看输出文件会不会多一行时间戳。
  4. 试着用 --greeting Bonjour 运行:nextflow run hello.nf --greeting Bonjour。

我第一次学 Nextflow 的时候,就靠这样不断改参数、加命令来熟悉这套模式。你也会很快上手的。

💡 查看工作目录
跑完后,去 ./work 目录看看。你会发现每个 process 都生成了一个哈希命名的文件夹,里面放着输入、脚本、输出和日志。这是 Nextflow 天然的“实验记录本”。

3.6 避坑指南

写第一个脚本时,有几个地方容易卡住。我直接列出来:

  • 忘写 nextflow.enable.dsl=2:报错信息会说 DSL2 constructs not recognized。看到这个,第一反应就是去检查头部。
  • process 内的脚本用错引号:script: 块要用三引号 """ 包裹。单引号或双引号在 Groovy 里有不同含义,容易出问题。
  • 忘了加 publishDir:不写这一行,输出文件只留在工作目录里,你很难找到。
  • 路径格式写错:publishDir "./results", mode: 'copy' 中间是逗号,不是冒号。

我曾经在一台新服务器上部署流程,忘了启用 DSL2 模式,结果花了一个小时检查为什么 workflow {} 就是不被识别。嗯,从那以后我每次新建脚本,第一件事就是敲那两行头部声明。

把这一节的内容消化掉,你就迈过了 Nextflow 入门的第一道门槛。接下来,等你用得更熟,会慢慢体会到这个脚本结构设计得有多巧妙。