4、第一个Nextflow脚本:Hello World示例、运行方式、日志输出结构

4.1 一个最简单的流程:Hello World

我个人习惯,学任何新工具都从“Hello World”开始。Nextflow 也不例外。虽然它专为生物信息学设计,但内核其实就一句话:定义 process,写一段脚本,跑起来看看。

先给你看一个最朴素的版本——没有输入文件,没有复杂通道,就一个 process 打印一句话:

// hello.nf
process sayHello {
    script:
    """
    echo 'Hello, Nextflow!'
    """
}

嗯,这就完成了一个工作流。你可能会说:“就这?” 对,就这。Nextflow 里每个 process 就像一个小箱子,箱子里的 script 就是你要执行的命令。它可以是 Shell、Python、Perl……任何你在终端里能跑的东西。

注意看这里的结构:

  • process 名称:sayHello,随便起,但最好有语义。
  • script 块:用三引号包裹(实际代码里是双引号?等会儿看),里边就是一段 Bash 脚本。
  • 没有 input、output 声明?对,可以省略。不过真实项目里几乎都会加上,否则没法串联流程。

小技巧:我第一次写 Nextflow 脚本时,以为 script 块里必须写 Groovy 语法。其实你写纯 Shell 就行,Nextflow 会自动帮你在集群上调度执行。省心。

4.2 运行你的第一个Nextflow工作流

文件保存为 hello.nf。打开终端,执行:

nextflow run hello.nf

就这么简单。你想想看,一个命令,背后发生了多少事?Nextflow 会解析脚本、创建临时工作目录、启动一个容器(如果没配容器就是本地环境)、运行脚本、收集日志。我刚开始用的时候,以为它会像普通脚本一样直接打印输出,结果发现……嗯,它把标准输出吞进日志文件里了。

正常的话,你会看到类似这样的信息:

N E X T F L O W  ~  version 23.10.0
Launching `hello.nf` [admiring_curie] DSL2 - revision: a1b2c3d4e5
executor >  local (1)
[a1/abc123] sayHello (1) | 1 of 1 ✔️

这行 [a1/abc123] sayHello (1) | 1 of 1 ✔️ 就是关键日志。告诉你:进程 ID 为 abc123(关联到 work 目录下的子文件夹),进程名 sayHello,第 1 个实例(如果你用通道可能会有多个),执行成功。

那真正的 “Hello, Nextflow!” 在哪里?它被写进了 work 目录下的 stdout 文件里。去翻一下:

cat work/a1/abc123/.command.out

或者用 Nextflow 提供的命令直接查看某个进程的输出:

nextflow log —output

不过更常用的方式是先保存结果文件,咱们后面几章会讲。

避坑指南:我曾经在某次集群训练中,跑完 Hello World 后发现 control+C 后工作目录还锁着。原来 Nextflow 的 work 目录是默认不清理的,下次运行时如果名字撞了会报错。建议给每个项目单独建文件夹运行,或者设置 -work-dir 参数。

4.3 日志输出结构:读懂Nextflow在干什么

Nextflow 的日志输出信息量挺大。我把关键字段整理成一张表,你对照着看:

字段/行含义举例
Launching ...启动的脚本路径、运行唯一标识名(如 admiring_curie)和DSL版本Launching `hello.nf` [admiring_curie] DSL2
executor > local (1)本机执行器,运行了1个任务executor > local (1)
[a1/abc123] sayHello ...任务ID(目录哈希)、进程名、进度、状态图标[a1/abc123] sayHello (1) | 1 of 1 ✔️
Cached: ...任务被缓存(输入/脚本未变)时显示Cached: [b2/def456] sayHello
Work directory实际工作目录(可在 nextflow.config 中修改)work/(默认)
.command.sh进程实际执行的完整脚本(含环境变量等)在 work 子目录下
.command.out标准输出你脚本里 echo 的内容
.command.err标准错误报错信息
.exitcode退出码(0 代表成功)0

实际上,每个 process 运行后,都会在 work 目录下生成一个以哈希命名的子文件夹。这个文件夹里除了上述文件,还有你脚本里生成的所有中间文件。我建议你手动进去看看,能直观理解 Nextflow 是怎么隔离任务环境的。

核心理解:Nextflow 的工作流日志 ≠ 应用日志。你需要区分“管道执行日志”(我们看到的那几行)和“进程内部日志”(写在 .command.out 里的)。前者告诉调度情况,后者告诉你具体软件跑的对不对。

4.4 Hello World 工作流执行过程(SVG 流程图)

下面这张图帮你直观理解刚才发生的事:

Hello World 工作流执行过程 流程入口 (无输入文件) sayHello script: echo 'Hello...' 工作目录: work/xx/ .command.out "Hello, Nextflow!" ⚡ 运行命令: nextflow run hello.nf 📁 日志文件位置: work/哈希/.command.out

注意看工作目录的创建和脚本执行是自动的。你写的 script: 块会被包装成 .command.sh,在隔离的文件系统里跑。这样做的好处很明显:同一个流程跑几十次也不会互相干扰。

4.5 完整示例与运行示例

我们再做一个稍微真实点的版本——加上 output 声明和输入参数:

// hello_v2.nf
params.greeting = "Hello, World!"

process sayHello {
    output:
    stdout

    script:
    """
    echo '${params.greeting}'
    """
}

workflow {
    sayHello()
    sayHello.out.view { it }
}

运行:

nextflow run hello_v2.nf --greeting "Hello, RNA-seq!"

日志输出大概长这样:

N E X T F L O W  ~  version 23.10.0
Launching `hello_v2.nf` [laughing_darwin] DSL2 - revision: a1b2c3d4e5
executor >  local (1)
[ae/123456] sayHello (1) | 1 of 1 ✔️
Hello, RNA-seq!

最后那行 "Hello, RNA-seq!" 就是通过 .view 打印到控制台的。你看到了吗?output 声明了 stdout,然后 workflow 里用 sayHello.out.view 把标准输出通道里的内容打印出来。这种串联方式,后面会反复用到。

小经验:实际项目中,我通常不会在 process 里直接 echo 结果,而是把结果写到文件,再用 output: path("result.txt") 来传递给下游。不过 Hello World 阶段,理解 stdout 输出和通道的概念更重要。

好了,第一个 Nextflow 脚本你就到手了。试着修改 greeting 参数,或者再写一个 process 拼接两句话。跑起来,看看 work 目录里都多了些什么文件——这才是熟悉 Nextflow 最直接的方式。