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 流程图)
下面这张图帮你直观理解刚才发生的事:
注意看工作目录的创建和脚本执行是自动的。你写的 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 最直接的方式。