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)。我画了一张图,帮你把这三个组件的关系理清楚:

Nextflow 脚本基本结构 params 定义 配置参数与默认值 process 定义 封装的命令或脚本 workflow 定义 调用进程,串联执行逻辑 workflow 是入口,process 是执行单元,params 是全局配置

这三个组件的分工很清晰:

  • 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管道,让流程真正流动起来。