第4章:Nextflow基础语法——参数、脚本、执行

学Nextflow,说白了就是搞明白三件事:参数怎么传、脚本怎么写、进程怎么跑。我刚开始接触时也觉得概念多,但拆开来看,其实每个部分都挺直白的。这一章咱们就把这三个核心要素捋清楚。

4.1 参数系统:让工作流学会"看人下菜碟"

工作流写死了肯定不行。你想想,今天跑A项目,明天跑B项目,数据路径、输出目录、阈值参数全都不一样。所以Nextflow设计了params这套机制。

4.1.1 参数定义

在脚本里用params.xxx定义一个参数,再给它一个默认值。这是最基础的用法。

// main.nf
params.reads = "$projectDir/data/reads/*.fastq"
params.outdir = "./results"
params.genome = "hg38"
params.pval = 0.05

我个人习惯把params定义放在脚本最前面,一眼就能看到所有可调参数。这样别人接手你的工作流,不用翻到底层逻辑,改改参数就能跑。

4.1.2 命令行覆盖参数

运行时用--参数名 值就能覆盖默认值。注意是两个短横线,不是单个。单个短横线那是给Nextflow自己用的指令参数。

nextflow run main.nf --reads "/data/project/samples/*.fastq" --pval 0.01
小技巧: 命令行传参的优先级最高。它 > 配置文件 > 脚本内默认值。调试阶段我常用命令行直接覆盖,不改源码。

4.1.3 使用配置文件

项目大了之后,参数会越来越多。全堆在命令行里既不优雅也不好维护。这时nextflow.config就派上用场了。

// nextflow.config
params {
    reads   = "$projectDir/data/reads/*.fastq"
    outdir  = "./results"
    genome  = "hg38"
    pval    = 0.05
}

process {
    executor = 'slurm'
    queue    = 'normal'
}

你看,配置文件和脚本分离,参数归参数,逻辑归逻辑。我在项目中遇到过团队协作时有人在脚本里硬编码路径,结果换环境就炸了……从那以后我强制要求所有可变字段走params。

4.2 脚本块:工作流真正干活的地方

进程里的script块,说白了就是告诉计算机"这一步你要执行什么命令"。它支持多种语言,但最常用的是Bash。

4.2.1 基础写法

process fastqc {
    input:
    path reads

    output:
    path "${reads}.html"
    path "${reads}.zip"

    script:
    """
    fastqc -o . $reads
    """
}

注意外面的双引号。你要是用单引号,变量就不会被解析。嗯,这里有个坑——我几年前就因为引号用错,折腾了整整一个下午……

小心: script块默认使用Bash。如果你想用其他语言,必须在shebang里指定。python、R、awk都可以,但记得写清楚。

4.2.2 多语言脚本

有些场景下Bash不够用,比如需要复杂的数据处理。Nextflow允许你在script块里直接嵌入Python或R。

process stats_plot {
    input:
    path data

    output:
    path "plot.png"

    script:
    """
    #!/usr/bin/env python3
    import pandas as pd
    import matplotlib.pyplot as plt

    df = pd.read_csv("$data")
    plt.figure(figsize=(10,6))
    df['value'].hist()
    plt.savefig('plot.png')
    """
}

我曾经在生信项目里遇到过样本量超过10万,Bash处理实在太慢,换成Python脚本后速度提升了一个数量级。灵活切换语言,这才是Nextflow的爽点。

4.2.3 字符串引号的学问

脚本块里的字符串处理是个容易翻车的地方。我总结了一下:

写法 变量解析 适用场景
""" ... """ 解析 $变量 常用,含变量拼接
''' ... ''' 不解析,纯文本 含大量$符号的代码
"..." 解析 $变量 单行简单命令
'...' 不解析,纯文本 单行纯字符串

怎么选?如果你脚本里有$开头的shell变量或者Perl正则,就用三单引号,否则Nextflow会尝试解析它,导致报错。

4.3 进程执行:理解背后的"调度"逻辑

进程是Nextflow的最小执行单元。你定义好输入、输出、脚本,Nextflow负责调度它什么时候跑、在哪里跑。

4.3.1 进程基本结构

process 进程名 {

    // 指令区:资源、执行器、标签
    executor 'slurm'
    cpus 4
    memory '8 GB'

    // 输入声明
    input:
    path fastq
    val sample_id

    // 输出声明
    output:
    path "${sample_id}_aligned.bam"

    // 执行脚本
    script:
    """
    bwa mem -t 4 $fastq | samtools sort -o ${sample_id}_aligned.bam
    """
}

指令区、输入、输出、脚本——就这四个部分。我这里有个习惯:输入输出一定要写清晰,命名别偷懒。否则工作流一旦复杂起来,你自己都搞不清哪个文件从哪来的。

4.3.2 输入输出声明的作用

你可能会问:为什么Nextflow非要我写input和output?让它自己推不行吗?答案很直接——不行。Nextflow靠这些声明来构建依赖图。只有明确知道每个进程要什么、产什么,它才能决定谁先跑、谁后跑、哪些可以并行。

核心认知: input/output声明 ≈ 工作流的"契约"。你写清楚,Nextflow帮你自动编排。你糊弄它,它就在运行时给你"惊喜"。

4.3.3 进程的执行机制

每个进程默认是串联执行的——上一个完了下一个才开始。但加上.set()和channel操作,就能变成并行。

workflow {
    // 读取样本列表
    Channel.fromPath(params.sample_list)
        | splitCsv(header: true)
        | map { row -> [row.sample_id, file(row.fastq)] }
        | set { samples }

    // 并行执行fastqc
    fastqc(samples)
    // 等所有fastqc完成后,执行汇总
    multiqc(fastqc.out.collect())
}

这里fastqc会对每个样本并行执行。我曾经管理过2000个样本的项目,靠这种并行机制,整个分析周期从两周压缩到两天。这就是Nextflow的威力。

4.4 三者如何协同:一个完整示例

光讲概念不过瘾。我们写一个完整的例子,把参数、脚本、执行串起来。

// main.nf
params.reads = "$projectDir/data/*.fastq"
params.outdir = "./results"
params.genome = "hg38"

process align {
    input:
    path reads
    val genome

    output:
    path "${reads.baseName}_sorted.bam"

    script:
    """
    bwa mem $genome $reads | samtools sort -o ${reads.baseName}_sorted.bam
    """
}

process count {
    input:
    path bam

    output:
    path "${bam.baseName}_counts.txt"

    script:
    """
    featureCounts -a genes.gtf -o ${bam.baseName}_counts.txt $bam
    """
}

workflow {
    Channel.fromPath(params.reads)
        | map { reads -> [ reads, params.genome ] }
        | set { read_pairs }

    align(read_pairs)
    count(align.out)
}

这个流程很简单:传参→比对→计数。但你看,参数定义了输入源和输出位置,脚本定义了每一步的具体命令,而workflow决定了执行顺序和依赖关系。三者缺一不可。

核心总结: params让工作流"灵活",script让工作流"能干",process/workflow让工作流"有序"。掌握这三块,你就拿到了Nextflow的入场券。

本章知识结构

下面这张图把参数、脚本、执行三者的关系可视化了一下。你可以把它当成Nextflow基础语法的"骨架"来记。

Nextflow 基础语法核心流程 params 参数定义 默认值 / 命令行覆盖 nextflow.config 环境/项目级配置 process 进程 input / output / 指令区 script 脚本块 workflow 工作流执行 自动编排 / 依赖解析 / 并行 关键提示 参数通过 $params.xxx 在脚本中引用

从这张图可以看得很清楚:参数定义和配置文件位于顶层,它们共同决定了"跑什么";中间的进程封装了脚本块,决定了"怎么跑";最底层的工作流执行负责调度编排,决定了"谁先跑、谁后跑"。三个层级之间通过箭头传递数据和依赖。

好了,这一章的内容就这么多。参数、脚本、执行——你把这三样东西刻在脑子里,Nextflow的基础语法就算拿下了。后面章节我们会在这个基础上继续往上搭,把工作流写得更健壮、更工程化。