4. 第一个Nextflow脚本:编写“Hello World”流程
先别急着碰那些复杂的质控工具。我们先用一个最经典的例子——Hello World——把Nextflow的基本用法踩一遍。我自己带团队的时候,发现很多人一上来就抄别人的复杂流水线,结果跑不通都不知道从哪里查。
先从一个小目标开始:让Nextflow帮我们打印一句“Hello, Nextflow!”。
4.1 环境准备好了吗?
在写脚本之前,确认你的终端里已经装好了Nextflow。敲下面这行命令:
nextflow -version
如果看到类似这种输出,说明环境OK:
N E X T F L O W
version 23.10.0 build 5895
created by Paolo Di Tommaso
没有的话,去装一个。我习惯用Conda或者直接下载二进制文件,具体方法上一章已经讲过了。
4.2 创建你的第一个脚本
新建一个文件,叫 hello.nf。对的,后缀就是 .nf。把下面的代码贴进去:
#!/usr/bin/env nextflow
params.greeting = "Hello, Nextflow!"
process sayHello {
output:
stdout
script:
"""
echo '${params.greeting}'
"""
}
workflow {
sayHello()
}
嗯,就这么几行。别急着运行,我们先把每个部分拆开看明白。
4.3 脚本的基本骨架
一个Nextflow脚本,说白了就是三部分:参数定义、流程定义(process)和工作流定义(workflow)。我画了一张图,帮你把这三个组件的关系理清楚:
这三个组件的分工很清晰:
- params:定义可以外部传入的参数,给整个流程一个默认值
- process:真正干活的单元。里面可以写任何shell命令,甚至可以调用Python或R脚本
- workflow:流程执行的入口。在这里调用process,把数据传进去,把结果传出来
4.4 每个组件都干了什么?
我们回到刚才的脚本,逐行看一下:
4.4.1 params 定义
params.greeting = "Hello, Nextflow!"
这行就是设置一个叫 greeting 的参数,默认值是 "Hello, Nextflow!"。你需要改变这个值的时候,不用修改代码,直接在命令行传参就行。怎么传?一会儿示范。
4.4.2 process 模块
process sayHello {
output:
stdout
script:
"""
echo '${params.greeting}'
"""
}
这块是流程里最核心的单元。每个 process 就像一个独立的函数,有自己的输入、输出和执行脚本。
- output: stdout:告诉Nextflow,这个进程的输出是标准输出(就是打印到终端的内容)。
- script::后面跟的
""" ... """里面装的就是要执行的shell命令。 - ${params.greeting}:这里用
${}来引用之前定义的参数。Nextflow会自动把变量的值替换进去。
小技巧:我建议你在 script 块里尽量用双引号的字符串 """,因为Nextflow可以做变量替换。单引号 ''' 就不会替换变量,适合纯静态命令。
4.4.3 workflow 入口
workflow {
sayHello()
}
这就是整个流程的入口。你调用 sayHello(),Nextflow就会去执行上面定义的那个process。等process跑完,这条workflow也就结束了。
注意:workflow块是必须的。我见过有人把process定义在末尾,忘了加workflow块,结果脚本跑不起来。记住,process可以定义很多个,但流程的执行入口只能是workflow。
4.5 运行你的第一个流程
把脚本保存好,在终端里执行:
nextflow run hello.nf
你会看到类似这样的输出:
N E X T F L O W ~ version 23.10.0
Launching `hello.nf` [serene_pasteur] DSL2 - revision: 127caa0e9f
executor > local (1)
[59/5b8d24] process > sayHello (1) [100%] 1 of 1 ✔
Hello, Nextflow!
看到了吧?最后一行打印了我们想要的问候语。前面那些信息是Nextflow自己的日志:
- 第一行告诉你Nextflow的版本号。
- 第二行显示启动的脚本名和一个随机生成的运行ID(
serene_pasteur这种)。 - 第三行说明用的是本地执行器。
- 第四行是进程的执行状态——
sayHello (1)表示这个进程跑了1个任务,[100%] 1 of 1 ✔代表全部成功。
如果你想换个问候语,不需要改脚本。试试这个:
nextflow run hello.nf --greeting "你好,Nextflow!"
输出变成:
你好,Nextflow!
--双横线后面跟参数名,就可以覆盖脚本里 params. 定义的默认值。这个特性在做多环境配置时特别有用。
4.6 常见避坑指南
| 常见错误 | 原因 | 解决办法 |
|---|---|---|
Unknown process 'sayHello' |
workflow块里调用的process名称写错了 | 检查process名的大小写和拼写 |
unresolved parameter 'greeting' |
用 ${greeting} 而不是 ${params.greeting} |
加上 params. 前缀 |
| 终端打印乱码/中文不显示 | 终端编码不是UTF-8 | 设置 export LANG=en_US.UTF-8 |
| 脚本卡住不执行 | workflow块和process定义顺序错了 | 把workflow放在文件末尾,或至少在process之后 |
核心要点:管道是用 | 来串联的,但在Nextflow里,串联是靠channel和process的连接来实现的。虽然Hello World还没用上channel,但请记住这个本质:process之间通过channel通信,而不是靠临时文件。
4.7 我能做个小实验吗?
当然。我建议你动手改几个地方看看效果:
- 把
echo '${params.greeting}'改成echo 'Hello World',看看脚本还认不认--greeting参数。 - 在script块里加一行新命令,比如
date,看看进程能不能执行多条命令。 - 试试不加workflow块,直接运行脚本,看看Nextflow会报什么错。
为什么要做这些?我自己的经验就是,只有亲手把脚本改坏了再修好,才能真正理解它的边界。读十遍不如跑一遍。
好了,你已经写好了第一个Nextflow脚本,也知道了它的三大组成部分:params、process、workflow。下面我们会在这个基础上,加入输入输出和channel管道,让流程真正流动起来。