研究管道
目标:从干净的代码检出加上原始 data/ 目录,运行 bash pipeline/run_pipeline.sh 即可重新生成所有已处理的数据集、分析结果、报告和手稿输出。每个产生下游使用的输出的脚本,都由 pipeline/ 下的某个脚本按依赖顺序调用。
布局
pipeline/
_common.sh # 共享辅助函数,被每个 run_*.sh 引用(不直接运行)
run_pipeline.sh # 顶层编排器:按顺序调用各个阶段脚本
run_preprocessing.sh # 阶段:src/prepro/*.R -> output/prepro/
run_analyses.sh # 阶段:src/analysis/*.R -> output/analysis/
run_reports.sh # 阶段:src/reports/*.qmd -> output/reports/
run_manuscript.sh # 阶段:src/manuscript/* -> output/manuscript/
run_script.sh # 使用相同的日志方式运行任意单个 .R / .qmd 文件
阶段名称遵循项目的 src/ 文件夹(例如,noh-survey 使用 src/manu/ 和 run_manu.sh)。仅当相应文件夹存在时才添加阶段;不要凭空创建空的阶段。
约定
- 每个脚本中都使用
#!/usr/bin/env bash和set -euo pipefail。 - 每个脚本都包含头部注释:说明其功能、用法行(
bash pipeline/<name>.sh)以及必须首先运行的阶段(需要 ...)。 - 脚本通过
_common.shcd到项目根目录,这样它们可以从任何目录工作。所有内部路径都相对于项目根目录。 - 记录日志:
run_pipeline.sh导出PIPELINE_LOG_DIR=logs/run_pipeline_<timestamp>。每个阶段都会创建${PIPELINE_LOG_DIR:-logs}/run_<stage>_<timestamp>/,因此当作为管道的一部分运行时,阶段日志嵌套在管道运行日志下;当独立运行时,则放在logs/下。每个 R 脚本 / Quarto 文档都会记录到自己的<name>_<HH-MM-SS>.txt文件中。 - 失败时,
run_r/render_qmd会打印失败的脚本、其日志路径以及日志的最后 20 行,然后停止整个管道。 - 在阶段内部,根据依赖深度将调用分组到
echo "=== Layer N: <原因> ==="标题下。对于依赖关系不明显的任何调用,添加尾部注释(# 需要 output/prepro/redcap-joined.rds)。跨阶段依赖关系有自己标记的块(例如,需要 PCA 结果的预处理脚本放在 PCA 之后的分析阶段)。 - 每个阶段
mkdir -p它要写入的输出目录。 - Quarto 会渲染到源文件夹中,因此
render_qmd会将结果移动到output/。当文件夹中的每个报告都以相同方式渲染时,使用for qmd in src/reports/*.qmd循环(跳过模板)。 - 在慢速步骤前添加
echo "注意:... 可能需要很长时间"。 - 保持调用显式,每个脚本一行。不要使用通配符自动发现 R 脚本:顺序就是依赖关系图,而且应该是可读的。
模板
从该技能的 templates/ 目录中复制这些模板并填入内容:
| 模板 | 成为 |
|---|---|
_common.sh | pipeline/_common.sh(直接复制) |
run_pipeline.sh | pipeline/run_pipeline.sh |
run_stage.sh | 每个阶段对应一个 pipeline/run_<stage>.sh |
run_script.sh | pipeline/run_script.sh(直接复制) |
_common.sh 提供:
init_log_dir <阶段名称>:创建并设置LOG_DIRrun_r <script.R> [参数...]:运行 R 脚本,标准输出+标准错误重定向到其日志(参数会添加到日志名称中,适用于参数化运行,如 noh-survey 的组循环)run_r_console <script.R> <stdout 文件>:标准输出保存为可交付成果(例如output/manuscript/<名称>-console.txt),标准错误重定向到日志render_qmd <doc.qmd> <目标目录或文件> [quarto 参数...]:渲染并移动输出;传递文件路径作为目标以重命名(例如,每个-P group:...生成一个 PDF)find_quarto:回退到 RStudio/Positron 捆绑的 Quarto
工作流程
1. 调查项目
- 列出
src/(以及fns/、config/)和任何现有的pipeline/。如果pipeline/已存在,在进行任何更改前阅读每个脚本,并在其风格与这些约定不同时保留其风格(在迁移已建立的管道前先询问)。 - 阅读项目的 CLAUDE.md / README 以了解声明的运行顺序。
2. 制定依赖顺序
对于 src/ 中的每个 .R / .qmd 文件,找出它读取和写入的内容:
grep -nE 'readRDS|read_rds|read_csv|read_excel|read\.csv|load\(|fread|readMat|source\(' <文件>
grep -nE 'saveRDS|write_rds|write_csv|ggsave|save\(|writexl|write\.csv|pdf\(|png\(|tiff\(' <文件>
将每个读取操作与写入该文件的脚本匹配。没有生产者的读取必须是 data/ 中的原始输入;如果不是,则标记它(这要么是缺失的管道步骤,要么是手动制作的输出)。分配层:第 0 层仅读取 data/;第 N 层读取来自 < N 层的输出。在编写脚本之前,向用户展示任何非明显情况的最终顺序。
跳过 archive/、misc/、模板(*template*)和草稿脚本,除非用户另有说明,并列出跳过的内容。
3. 编写脚本
复制模板,填入阶段和层,chmod +x pipeline/*.sh。
4. 检查覆盖率和语法
bash <此技能目录>/scripts/check_coverage.sh <项目根目录>
该脚本对每个管道脚本运行 bash -n 和 shellcheck(如果已安装),然后列出 src/ 中未被任何管道脚本通过名称提及的文件。由通配符循环渲染的文件会显示为未提及,因此需要手动检查。所有剩余文件要么被添加到管道中,要么被确认为有意排除。
5. 可复现性审计
搜索 src/ 并报告(而不是静默修复)以下任何情况:
setwd(或绝对路径(/Users/、C:/、~/):会在其他机器上破坏- 随机性(
sample(、rnorm(、boot、simr、缺少set.seed)而没有set.seed() - 仅交互式代码:
View(、file.choose(、readline(、browser( - 写入
output/之外(除了 Quarto 的渲染然后移动) rm(list = ls()):当脚本在同一个会话中运行时,会隐藏缺失的依赖关系
6. 运行它
完整管道可能需要很长时间并且会覆盖 output/,因此在运行前询问。提供先运行单个阶段或对单个文件运行 run_script.sh 的选项。要检查从头开始的可复现性,将 output/ 移走(mv output output.bak),不要删除它,然后运行 run_pipeline.sh 并进行比较。
7. 文档化
在项目的 CLAUDE.md / README 中添加或更新“运行管道”部分:一条命令、每个阶段的作用以及如何运行一个脚本。
向现有管道添加脚本
- 找出新脚本读取的内容(步骤 2),从而确定它属于哪个阶段和层。
- 在那里添加一行
run_r,如果依赖关系不明显,则添加依赖注释。 - 如果它的输出为后续脚本提供输入,请检查这些脚本是否仍在更靠后的层中。
- 运行
scripts/check_coverage.sh。