第一个批质控管道:编写最简单的FastQC管道、运行与查看结果

好了,前面的章节我们把Nextflow的环境搭好了,核心概念也聊了一遍。现在该动手了。我自己的习惯是——学任何新工具,先跑通一个最简单的例子再说。别一上来就折腾复杂的多步流程,那样出了问题你都不知道是哪儿崩的。

所以这一章,我们就写一个只做FastQC的管道。输入一批FASTQ文件,挨个跑FastQC,然后把结果收拢到一起。你想想看,这个场景在生物信息里多常见:新拿到一批测序数据,第一件事就是看看质量咋样。

4.1 准备工作:目录结构与模块

在写脚本之前,先把工作区整理好。我个人习惯是每个项目建一个独立的文件夹,里面按功能分几个子目录:

fastqc_demo/
├── input/            # 放FASTQ文件(或软链接)
├── output/           # 跑完后结果放这里
└── main.nf          # Nextflow脚本

输入数据:你可以从公共数据库下载几个小FASTQ做测试,或者用seqkit模拟几条reads。我不建议拿真实大样本跑第一个demo,跑半天卡住了你都不知道是不是脚本写错了。我曾经见过一个同学直接拿全基因组数据试,结果跑了三小时才发现FastQC参数写反了……

小提示:seqkit sample -n 10000 big.fastq.gz > small.fastq 可以快速抽一万条reads,够你测试用。

模块加载:如果你用集群或者conda环境,记得保证fastqc命令在$PATH里。或者更推荐的方式是在nextflow.config里声明conda环境,后面我们会细讲。这一章为了简单,我们假设fastqc已经装好了。

# 验证一下
fastqc --version
# 输出类似:FastQC v0.12.1

4.2 第一个main.nf:就一个process

打开main.nf,我们写一个最简单的Nextflow管道。嗯,这里要注意——不要一开始就写workflow块,先从一个process开始,把逻辑讲清楚。

#!/usr/bin/env nextflow

params.input = "$projectDir/input/*{1,2}.fastq.gz"

process fastqc {

    input:
    path read

    output:
    path "${read.baseName}_fastqc.html"
    path "${read.baseName}_fastqc.zip"

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

workflow {
    reads_ch = Channel.fromPath(params.input)
    fastqc(reads_ch)
}

这段代码不长,但包含了Nextflow管道的骨架:process定义、input/output声明、script执行命令,以及workflow里channel的创建与调用。

  • params.input:用参数驱动输入路径,方便将来调整。字符串里通配符*{1,2}.fastq.gz会匹配类似sample1_1.fastq.gz和sample1_2.fastq.gz这样成对的文件。
  • path read:声明一个路径类型的输入。Nextflow会在执行前把文件stage到工作目录。
  • output:两个产物——HTML报告和zip压缩包。文件名用了${read.baseName},这是Nextflow提供的变量,会去掉后缀得到纯文件名。
  • script:就是shell命令。注意这里-o .表示输出到当前目录,Nextflow的工作目录是独立创建的,不会污染源数据。
  • workflow:创建reads_ch channel,传入process。每个文件都会被当成一个独立的job并行执行。
这里面最关键的一点是:Nextflow自动帮你管理了并行和文件隔离。你不需要写for循环,也不需要担心输出文件名冲突——每个job都在自己的工作目录里跑,结果最终会按output声明收集回来。

4.3 运行管道

写好了脚本,怎么跑?直接在命令行执行:

cd fastqc_demo
nextflow run main.nf --input 'input/*.fastq.gz'
避坑指南:我曾经因为忘记加引号导致Shell展开了通配符,Nextflow收到了一大堆乱七八糟的字符串。记得把--input的参数用单引号包起来,或者用双引号但转义。更稳妥的做法是在main.nf里用params.input定好默认值,运行时就不必每次敲了。

正常输出应该类似这样(注意看日志里的进程名和状态):

N E X T F L O W  ~  version 24.10.0
Launching `main.nf` [romantic_tesla] DSL2 - revision: xxxxxx
executor >  local (4)
[4d/8f2a03] process > fastqc (1) [100%] 4 of 4 ✔

看到4个进程全部成功(4 of 4 ✔),大功告成。如果看到FAILED,别慌——去work/目录下找对应的task目录,里面有.command.sh、.command.out、.command.err,一步步排查。

4.4 查看结果

管道默认把输出放在output/吗?不是的。上面脚本里output声明只是告诉了Nextflow“这个process会生产这些文件”,但最终结果会按publishDir指令放到指定目录。为了简化,我们先不加publishDir,结果会留在work/各自的task目录里。但你可以用下面命令把报告汇总到results/:

find work/ -name '*_fastqc.html' -exec cp {} results/ \;
find work/ -name '*_fastqc.zip' -exec cp {} results/ \;

然后打开任意一个HTML文件,你就能看到经典的FastQC图表了:Per base sequence quality、Per sequence GC content、Adapter Content……这些我就不展开了,不是本章重点。重点是——你通过一个不到20行的脚本,完成了一整批文件的质控,而且它是可重复的、并行的、避免污染原始数据的。

4.5 一张图看懂这个管道

下面这张SVG图概括了我们刚才做的每一步的核心逻辑。你记住这个结构,后面无论管道复杂多少倍,骨架都是这样的。

最简单FastQC管道流程 FASTQ文件列表 Channel.fromPath process fastqc input: path read → 执行 fastqc -o . $read 并行(N个样本) 并行(N个样本) *.html + *.zip 报告 (每个样本独立输出) 关键点 • 文件隔离:每个job 有自己的工作目录 • 自动并行:无需写 循环或调度脚本

4.6 常见问题与调试技巧

问题 可能原因 解决方法
没有匹配到任何文件 通配符写错,或路径不对 用ls检查路径,或者用nextflow run ... --input 'input/*.fastq.gz' -resume并看日志
进程一直挂起 executor配置不对(默认local一般没问题) 检查是否写了对集群executor的引用,本地调试用-process.executor local
FastQC报No such file input声明的path没有正确stage 确认文件没有被其他进程删除,或者用file类型而非path(但path更推荐)
多个样本共享同一个输出目录 没有用publishDir或者用了但不带mode: 'copy' 加publishDir 'results', mode: 'copy'在process顶部

因为我们是第一次写管道,我建议你先把最精简的版本跑通,再加publishDir、资源限制、conda环境这些花活。一步到位往往不是效率最高的学习方式。

好了,现在你手头应该有一个能运行的FASTQC管道了。哪怕它很简单,但并行、可重复、可参数化这几个Nextflow的核心理念你都已经亲手实践了。后面的章节,我们会往里面加MultiQC汇总、加样本表驱动、加资源管理……你会看到,底层的设计思路跟这个20行的demo是一脉相承的。