跳转到内容
GitHub

主题 / 语言

15 脚本与配置语言

Shell 和 PowerShell 脚本、任务入口(just、Task、Make)、YAML/JSON/TOML 配置、.env 文件、Dockerfile、SQL 文件和 CI 配置文件怎么写、用什么工具检查。

草稿复核于 2026-11-14 · 下次复核 2027-05-1440 条规则 · 硬约束 26 条在 GitHub 上修改

一页结论

  1. Shell 只做胶水。 调用现成命令、串几步操作用 Shell;出现复杂分支、并发、要解析 JSON/YAML 时,改用项目的主语言。
  2. 先写对解释器。 可执行脚本写 shebang(首行的 #! 解释器声明);sh 脚本里不用 Bash 专有语法;PowerShell 单独一套工具。
  3. 每个脚本都过 ShellCheck 和 shfmt。 包括没有 .sh 后缀的可执行文件。set -euo pipefail 是护栏,不是异常处理。
  4. 一个任务入口,一套任务名。 统一的是 dev/test/lint/format/check/build/clean 这些名字,不统一工具;check 不改文件,失败不能被吞掉。
  5. 配置先能解析,再过 Schema。 按消费者选格式;机器读的配置配一份入库的 JSON Schema;拒绝重复键;YAML 里会被自动转类型的值加引号。
  6. .env 不入库。 入库的是只含变量名和非敏感默认值的 .env.example;生产环境由部署平台注入。
  7. Dockerfile 要过 lint 也要真构建。 固定基础镜像、非 root 运行、构建凭据走 secret 挂载、有 .dockerignore。
  8. SQL 文件声明方言。 SQLFluff 通过只说明格式和部分语法没问题,不代表能在目标数据库上执行。
  9. CI 配置是带权限的程序。 跑 actionlint 和 zizmor;第三方 Action 锁到完整提交编号;权限写最小;不可信文本不拼进 run:。

规则

级别
检查方式
显示 40 条
CFG-001硬约束检查:自动脚本与配置

可执行脚本首行必须写 shebang,且与实际语法一致;用 #!/bin/sh 的脚本里不写 Bash 专有语法

怎么检查:ShellCheck 对缺少 shebang 报 SC2148;按 shebang 判定方言后,对 sh 脚本里的 Bash 语法报 SC3xxx 系列警告
为什么、来源
为什么:解释器写错时脚本在开发机上能跑、换到只有 dash 或 BusyBox 的环境里就出错,而且错误往往不在第一行就暴露。
  • Bash 脚本用 #!/usr/bin/env bash;需要在最小镜像或系统初始化阶段运行的才写 POSIX sh。
  • 要跨平台运行的脚本,写明支持哪些环境并逐个实测:macOS 自带的 Bash 版本很旧、BusyBox ash、Git Bash、WSL 都不能当成同一个环境。
  • zsh 适合交互使用,不作为自动化脚本的默认解释器。
CFG-002硬约束检查:自动脚本与配置

仓库里所有 Shell 脚本(包括没有 .sh 后缀的可执行文件)在 CI 上跑 ShellCheck,有告警即失败

怎么检查:用 shfmt -f . 按后缀和 shebang 列出全部 Shell 文件,交给 shellcheck 检查
为什么、来源
为什么:只查 scripts/*.sh 会漏掉 bin/ 下没有后缀的脚本,而那往往是最常被执行的。

个别告警确实不适用时,在该行上方用 # shellcheck disable=SCxxxx 指明规则并写理由(见 COD-039),不整文件关闭。

CFG-003硬约束检查:自动脚本与配置

Shell 脚本用 shfmt 统一格式,CI 用 shfmt -d 检查;缩进等选项写在 .editorconfig 里

怎么检查:CI 运行 shfmt -d .,有差异即失败
为什么、来源
为什么:格式交给工具,评审才不用讨论空格;shfmt 会读取 .editorconfig,编辑器和 CI 用同一份设置。

格式化只管排版,不能替代 ShellCheck 的正确性检查。

CFG-004硬约束检查:自动脚本与配置

变量展开和命令替换一律加双引号;传多个参数用 "$@" 或数组,不拼成一个字符串

怎么检查:ShellCheck 的 SC2086(未加引号的展开)、SC2068(未加引号的 $@)等规则
为什么、来源
为什么:不加引号时,含空格、换行或通配符的值会被拆开或展开,轻则出错,重则删错文件。

只有确实要做通配符展开时才不加引号,并写注释说明。遍历文件不用 for f in $(ls),用 find -print0 配合 while read -r -d '',或直接用通配符。

CFG-005软指导检查:半自动脚本与配置

Bash 脚本开头默认写 set -euo pipefail;预期可能失败的命令用 if 或显式检查退出码,不把 -e 当异常处理

怎么检查:脚本扫描检查开头的 set 行;评审预期失败的命令是否显式处理
为什么、来源
为什么:-e 在 if 条件、&&/|| 链、部分函数调用里不会触发退出,指望它兜底会漏掉错误。
  • pipefail 让管道里任何一段失败都算失败;但 grep -q 这类提前退出的命令会让上游收到 SIGPIPE,此时要显式处理,而不是全局关掉。
  • 目标 sh 不一定支持 pipefail,写 POSIX sh 时要在目标 shell 上实测,或逐步检查返回码。
CFG-006硬约束检查:半自动脚本与配置

不可信输入不得进入 eval、bash -c、sh -c 或拼接出的命令字符串;命令和参数分开传

怎么检查:扫描 eval、-c 调用作为候选,评审其参数来源
为什么、来源
为什么:拼进命令字符串的外部内容会被当成代码执行,是命令注入的直接入口。
CFG-007硬约束检查:半自动脚本与配置

临时文件和目录用 mktemp 创建并用 trap 清理;rm -rf 用到的路径变量先确认非空且在受控目录内

怎么检查:ShellCheck 的 SC2115 提示用 ${var:?} 防止变量为空时删到根目录;评审删除路径的来源
为什么、来源
为什么:固定文件名的临时文件会被并发运行互相覆盖;变量为空时 rm -rf "$dir/"* 会删到意想不到的地方。
tmp_dir=$(mktemp -d)
trap 'rm -rf -- "$tmp_dir"' EXIT
CFG-008软指导检查:人工脚本与配置

错误信息写到标准错误,失败返回非零退出码;会改外部状态的脚本提供预演(dry-run)或先预检再执行

怎么检查:评审时看错误输出位置、退出码和危险操作前的预检
为什么、来源
为什么:调用方和 CI 靠退出码判断成败,靠标准输出拿结果;两者混在一起就没法组合使用。
CFG-009软指导检查:人工脚本与配置

脚本出现复杂分支、状态机、并发、重试或要读写 JSON/YAML 时,改用项目主语言写

怎么检查:评审新增或大幅修改的脚本时判断是否还适合用 Shell
参考值:Google Shell 风格指南建议超过约 100 行或控制流复杂时重新考虑;这是重新评估的信号,不是硬上限。
为什么、来源
为什么:Shell 没有可靠的数据结构和错误处理,逻辑一复杂就难测、难改。

只是读取或小改 JSON/YAML 时用 jq、yq 这类解析工具,不用 sed、grep 按文本改结构化数据。

CFG-010硬约束检查:自动脚本与配置

PowerShell 脚本在 CI 上跑 PSScriptAnalyzer,不用 ShellCheck 查 .ps1

怎么检查:CI 运行 Invoke-ScriptAnalyzer -Recurse,有错误级问题即失败
为什么、来源
为什么:PowerShell 是另一门语言,ShellCheck 读不懂它。

脚本开头写 Set-StrictMode -Version Latest 和 $ErrorActionPreference = 'Stop',让未定义变量和非终止错误也能暴露出来。测试用 Pester。

CFG-011软指导检查:半自动

仓库根目录提供一个任务入口(justfile、Taskfile.yml、Makefile 或 package.json 脚本),能列出全部任务及说明

怎么检查:检查根目录存在任务入口,just --list、task --list 等能列出任务;贡献说明里写明所需工具
为什么、来源
为什么:新人和 Agent 不用翻文档猜命令;CI 也能调用同一套命令(见 03)。
CFG-012软指导检查:半自动

适用的任务统一用这些名字:help、dev、test、lint、format、check、build、clean;不适用的不建空壳

怎么检查:脚本列出入口里的任务名,与约定名单比对
为什么、来源
为什么:名字统一后,在任何仓库里都知道该敲什么,Agent 也不用逐个仓库摸索。
  • lint:静态检查,不改文件。
  • format:格式化,会改文件。
  • check:lint、类型检查、快速测试等只读检查的合集,CI 和本地都跑它。
  • clean:只删本项目生成的文件。
CFG-013硬约束检查:自动

check、lint、test 任务不得修改工作区里的文件

怎么检查:CI 跑完 check 后执行 git status --porcelain,输出非空即失败
为什么、来源
为什么:检查任务顺手改了文件,CI 就会在改过的代码上判通过,而提交上去的却是没改的版本。
CFG-014硬约束检查:半自动

任务失败必须返回非零退出码;不用 || true、Make 的 - 前缀、Task 的 ignore_error、CI 的 allow_failure 吞掉检查失败

怎么检查:扫描任务文件和 CI 配置里的这些写法作为候选,逐个确认有理由且不是必过检查
为什么、来源
为什么:失败被吞掉后流水线一片绿,问题要到线上才暴露。

确实允许失败的步骤(如可选的上报),在旁边写注释说明理由。

CFG-015软指导检查:人工

部署、发布、重置数据这类有外部副作用的任务单独命名(如 deploy-staging、reset-db),不挂在默认任务、check 或 test 之下

怎么检查:评审任务入口时检查默认任务和检查类任务的依赖链
为什么、来源
为什么:敲一个看起来无害的命令却连带部署或清库,是最难追溯的事故之一。
CFG-016软指导检查:半自动脚本与配置

Makefile 里不对应文件的目标写进 .PHONY;配方用到 Bash 语法时显式设置 SHELL 和 .SHELLFLAGS

怎么检查:评审 Makefile;检查非文件目标是否在 .PHONY 中
为什么、来源
为什么:不声明 .PHONY 时,仓库里恰好有同名文件或目录,目标就会被跳过;Make 默认用 /bin/sh,Bash 语法会在部分系统上失败。

例如 SHELL := bash 和 .SHELLFLAGS := -euo pipefail -c。配方行必须用 Tab 缩进,在 .editorconfig 里给 Makefile 设 indent_style = tab。

CFG-017软指导检查:人工

按消费者选配置格式:机器之间交换用 JSON,已有生态约定的(CI、容器编排)用 YAML,人工编辑的应用配置可用 TOML;不为统一而强行转换

怎么检查:评审新增配置文件时确认格式是消费者原生支持的
为什么、来源
为什么:格式由读它的程序决定;强行统一成一种格式,往往要多一层转换,还会丢掉注释或类型。

环境变量只放部署时会变的简单值和密钥引用,不把整段 JSON 塞进一个变量。

CFG-018硬约束检查:自动

机器读取、结构固定的配置文件配一份入库的 JSON Schema,CI 用 check-jsonschema 校验;校验时使用本地 Schema,不联网拉取

怎么检查:CI 运行 check-jsonschema --schemafile schemas/xxx.json <配置文件>
为什么、来源
为什么:只检查「是合法的 YAML」挡不住拼错的键和错误的类型,这类错误往往到启动或运行时才暴露。
  • Schema 用 2020-12 版,写明 required、类型、枚举和取值范围;未知字段默认拒绝(additionalProperties: false),要扩展时再放开。
  • 校验分四步:能解析 → 符合 Schema → 业务语义正确(引用存在、取值组合合理)→ 不含明文密钥。
  • 配置结构有破坏性变化时,Schema 带版本号,并写清迁移办法。
  • 断网环境要把 Schema 及其引用的元 Schema 放在本地。
CFG-019硬约束检查:自动

YAML 和 JSON 配置不得出现重复键

怎么检查:YAML 用 yamllint 的 key-duplicates 规则(默认开启);JSON 用能检出重复键的工具,如 pre-commit-hooks 的 check-json 或 Biome 的 noDuplicateObjectKeys
为什么、来源
为什么:YAML 规范要求键唯一,JSON 规范说重复键的处理结果不可预测;不同解析器有的取前一个、有的取后一个,同一份配置在两个程序里含义不同。
CFG-020硬约束检查:半自动

YAML 里会被自动转换类型的值加引号,如 yes/no/on/off、像版本号的 1.10、带前导零的数字、12:30 这类时间

怎么检查:yamllint 的 truthy 和 octal-values 规则调成 error 级别;像版本号的浮点值靠评审和反序列化测试
为什么、来源
为什么:不同 YAML 解析器对这些值的处理不同:no 可能变成 false,1.10 可能变成 1.1,结果是配置被悄悄改了。

GitHub Actions 的 on: 是键不是值,给 truthy 规则设 check-keys: false 避免误报。

CFG-021硬约束检查:自动

YAML 文件在 CI 上跑 yamllint,配置文件入库;与格式器冲突的排版规则关掉,只留格式器管不到的规则

怎么检查:CI 运行 yamllint -s .(-s 让告警也失败)
为什么、来源
为什么:格式器只管排版,重复键、隐式类型、语法错误要靠 yamllint 发现。

同时用 Prettier 时,yamllint 的缩进、行宽等排版规则交给 Prettier,避免两个工具互相打架。

CFG-022硬约束检查:自动

JSON 和 YAML 用同一个格式器(默认 Prettier),CI 用检查模式验证

怎么检查:CI 运行 prettier --check "**/*.{json,yml,yaml}"
为什么、来源
为什么:一种文件只用一个格式器,不同人、不同编辑器保存后才不会产生无意义的改动。
  • TypeScript 项目已用 Biome 格式化 JSON 的,JSON 交给 Biome,Prettier 只管 YAML,不要两个都管 JSON(见 10)。
  • 带注释的 JSONC 只用于明确支持它的文件(如 tsconfig.json、编辑器设置),不把它当标准 JSON 交给其他解析器。
CFG-023硬约束检查:自动

TOML 文件用一个格式化和检查工具(默认 Tombi),CI 用检查模式验证

怎么检查:CI 运行该工具的检查模式(如 tombi format --check 和 tombi lint)
为什么、来源
为什么:TOML 的点号键、表数组、日期时间类型容易写错,靠肉眼发现不了。

已经在用 Taplo 的项目可以继续用,但它已转为个人维护、发布很少,新项目不再选它。工具通过不代表目标程序的 TOML 解析器行为一致,关键配置要用目标程序实际加载一次。

CFG-024硬约束检查:自动

.env 等本地环境文件写进 .gitignore,不入库;入库的 .env.example 只列变量名、说明和非敏感默认值

怎么检查:CI 检查版本库里没有除 .env.example 以外的 .env、.env.* 文件,并对仓库跑密钥扫描(工具见 25)
为什么、来源
为什么:.env 里通常有真实密码和令牌,一旦提交就进了历史,删文件也删不掉。

代码新增或删除了环境变量,同一个改动里同步更新 .env.example;可以用脚本比对代码读取的变量名和示例文件。

CFG-025软指导检查:半自动

环境变量名只用大写字母、数字和下划线,并带应用或服务前缀,如 ORDER_SVC_DB_URL

怎么检查:脚本检查 .env.example 和配置定义里的变量名格式
为什么、来源
为什么:POSIX 约定工具使用的环境变量名由大写字母、数字和下划线组成;加前缀避免和系统或其他程序的变量撞名。
CFG-026软指导检查:人工

.env 文件只用于本地开发;生产环境由部署平台或密钥服务注入,不把 .env 打进镜像

怎么检查:评审部署配置和 Dockerfile,确认没有复制 .env
为什么、来源
为什么:配置随部署变化、和代码分开存放;打进镜像的 .env 会随镜像四处分发。
  • .env 没有统一的语法标准,各加载库对引号、插值、多行的处理不同,只写简单的 KEY=value。
  • 脚本里不要 source 来路不明的 .env:它会被当成 Shell 代码执行。
CFG-027硬约束检查:自动

Dockerfile 在 CI 上跑 hadolint 和 docker build --check,并实际构建一次

怎么检查:CI 运行 hadolint Dockerfile 和 docker build --check .,再执行真实构建
为什么、来源
为什么:lint 能发现写法问题,但只有真的构建才能证明它能用;两者缺一不可。
  • hadolint 会对 RUN 里的 Shell 片段调用 ShellCheck。
  • RUN 里用了管道时,先设 SHELL ["/bin/bash", "-o", "pipefail", "-c"](hadolint DL4006),否则管道前段失败会被忽略。
  • .hadolint.yaml 入库,忽略某条规则要写理由。
CFG-028硬约束检查:自动

正式发布的镜像,FROM 不用 latest 或不带标签的基础镜像;用标签加 digest 固定,由依赖更新机器人提 PR 升级

怎么检查:hadolint 的 DL3006(未带标签)、DL3007(用了 latest);另用脚本检查 FROM 行是否带 @sha256:
为什么、来源
为什么:标签会被重新指向别的内容,不固定就说不清某次构建用的是哪个基础镜像。

固定 digest 不等于构建可复现:包下载、时间戳、编译器、架构都可能让两次构建结果不同。基础镜像怎么选见 26。

CFG-029硬约束检查:自动

最终镜像以非 root 用户运行;确实需要 root 的,写明原因和运行时的限制措施

怎么检查:hadolint 的 DL3002(最后的 USER 是 root);构建后运行 id 确认
为什么、来源
为什么:容器里的进程一旦被攻破,以 root 运行会让攻击者更容易逃逸或破坏宿主机挂载的数据。
CFG-030硬约束检查:自动

构建时需要的凭据用 BuildKit 的 secret 或 ssh 挂载传入,不用 ARG、ENV 或 COPY

怎么检查:docker build --check 的 SecretsUsedInArgOrEnv 检查;对 Dockerfile 跑密钥扫描
为什么、来源
为什么:ARG 和 ENV 的值会留在镜像元数据和构建历史里,拿到镜像就能读出来。
CFG-031硬约束检查:自动

有 Dockerfile 的目录必须有 .dockerignore,至少排除 .git、.env*、密钥文件、依赖和构建缓存目录

怎么检查:CI 检查 .dockerignore 存在并包含这些条目
为什么、来源
为什么:没有它时 COPY . . 会把 Git 历史和本地密钥一起拷进镜像,还会让构建缓存频繁失效。
CFG-032软指导检查:半自动

用多阶段构建,运行阶段只拷贝运行所需的文件;先拷依赖清单和锁文件安装依赖,再拷源码

怎么检查:评审 Dockerfile 的阶段划分和 COPY 顺序;对比镜像体积
为什么、来源
为什么:编译工具和源码留在运行镜像里既增大体积又扩大攻击面;先装依赖能让源码改动时复用依赖层缓存。

极简的单阶段镜像(如只拷一个静态二进制)可以例外。

CFG-033软指导检查:自动

系统包安装和清理缓存放在同一个 RUN 里;CMD 和 ENTRYPOINT 用 JSON 数组写法

怎么检查:hadolint 的 DL3009(未清 apt 列表)、DL3015(未加 --no-install-recommends);docker build --check 的 JSONArgsRecommended
为什么、来源
为什么:分开写时缓存会留在上一层里;Shell 写法的 CMD 让进程收不到停止信号,容器无法优雅退出。

系统包是否锁具体版本(DL3008)看基础镜像的更新策略;锁了要有人定期升级,否则会因旧版本被源删除而构建失败。

CFG-034硬约束检查:自动

仓库里的 SQL 文件用 SQLFluff 检查和格式化,.sqlfluff 入库并写明目标方言

怎么检查:CI 运行 sqlfluff lint;.sqlfluff 里必须有 dialect 设置
为什么、来源
为什么:不同数据库的 SQL 语法不同,不声明方言就只能按通用语法猜,结果既漏报又误报。
  • SQLFluff 通过只说明格式和它能解析的语法没问题;用了方言特有语法的 SQL 要在目标数据库的实际版本上执行测试(见 17)。
  • 兼容 PostgreSQL 的国产数据库不等于和 PostgreSQL 完全一致,同样要在目标库上实测。
  • 有模板的 SQL(如 dbt、Jinja)要配置对应的 templater。
CFG-035软指导检查:自动

业务查询写明要取的列,不用 SELECT *;EXISTS (SELECT 1 ...) 这类不取列的写法除外

怎么检查:在 .sqlfluff 中启用 AM04(结果列数不确定)
为什么、来源
为什么:表加列或调整列顺序后,SELECT * 的结果会悄悄改变,调用方可能因此出错或多取敏感字段。
CFG-036硬约束检查:自动

GitHub Actions 的 workflow 文件在 CI 上跑 actionlint 和 zizmor;GitLab CI 配置用官方 CI Lint 校验

怎么检查:CI 运行 actionlint 和 zizmor .github/workflows/;GitLab 调用项目的 CI Lint 接口
为什么、来源
为什么:通用的 YAML 检查看不懂 CI 语法和表达式;actionlint 查语法和类型,zizmor 查安全问题,两者互补。

GitLab 的 include、rules、变量要在项目上下文里才能完整校验,所以用实例自带的 CI Lint,而不是只跑 yamllint。

CFG-037硬约束检查:自动

第三方 Action 和可复用 workflow 锁到完整的提交 SHA(或使用平台提供的 workflow 依赖锁文件),不只写 @v4、@main

怎么检查:zizmor 的 unpinned-uses 检查
为什么、来源
为什么:标签和分支随时可以被改指向;被投毒的 Action 会在你的流水线里用你的权限运行。

SHA 后面加注释写明对应的版本号,交给 Renovate 或 Dependabot 升级(见 03)。GitHub 的 workflow 依赖锁文件(.github/workflows/actions.lock)是新功能,确认所用方案支持后再用。

CFG-038硬约束检查:自动

workflow 显式声明最小的 permissions,只给需要更高权限的 job 单独提升

怎么检查:zizmor 的 excessive-permissions 检查
为什么、来源
为什么:默认令牌的权限可能很宽;某一步被攻破时,权限越小损失越小。

来自 fork 的 PR 代码不和能读密钥或写权限的 job 混跑。GitLab 的敏感变量设为 protected 和 masked。

CFG-039硬约束检查:自动

PR 标题、分支名、提交信息等不可信的事件文本不直接写进 run: 脚本,先放进环境变量再引用

怎么检查:zizmor 的 template-injection 检查
为什么、来源
为什么:${{ }} 表达式会在脚本执行前被原样替换进去,PR 标题里的一段 Shell 代码就会被执行。
- env:
    PR_TITLE: ${{ github.event.pull_request.title }}
  run: printf '%s\n' "$PR_TITLE"
CFG-040软指导检查:人工

Cron 表达式旁写明由哪个调度器执行、时区和字段数;改动重要的定时任务时,用目标调度器列出接下来的若干次触发时间核对

怎么检查:评审时检查注释和触发时间列表
为什么、来源
为什么:Linux cron、GitHub Actions、Quartz 的字段数和语法不同;时区不写清、遇到夏令时,任务可能不跑或跑两次。
  • GitHub Actions 的 schedule 默认用 UTC,可以加 timezone 指定 IANA 时区;最短间隔 5 分钟,高峰时可能延迟。
  • Linux cron 的「日」和「星期」两个字段都有限制时,满足任一就会触发。
  • 定时任务的重复触发、漏触发和幂等见 18。

选型

类别默认备选(何时用)不推荐
Shell 静态检查ShellCheck · GPL-3.0(作为独立命令行工具在 CI 里运行;要随产品分发时见 27)
bash -n(只想快速查语法错误;不能替代 ShellCheck)
只靠人工评审:引号、方言这类问题肉眼很难看全
Shell 格式化shfmt · BSD-3-Clause——
Shell 脚本测试bats-core · MIT
脚本有分支逻辑、被多处调用,需要回归测试时
——
PowerShell 检查与测试PSScriptAnalyzer(静态检查)+ Pester(测试) · PSScriptAnalyzer 为 MIT,Pester 为 Apache-2.0—
用 ShellCheck 查 .ps1:不是同一门语言
任务入口just · just 为 CC0-1.0;Task 为 MIT;GNU Make 为 GPL-3.0 或更高版本
多语言仓库,主要是给一组命令起好记的名字
Task(需要任务依赖、按输入输出跳过已完成的任务,或希望在 Windows 上不依赖 Bash 时)
GNU Make(C/C++ 项目要按文件增量构建,或已有成熟的 Makefile)
npm scripts(只有一个 Node 包的项目)
一层套一层的包装脚本:出错时找不到真正执行的命令
为几条命令写复杂的 Makefile:Tab、变量展开、隐式规则带来额外的坑
JSON Schema 校验check-jsonschema · check-jsonschema 为 Apache-2.0
应用内的 JSON Schema 校验库(配置在程序启动时校验,各语言的校验库见 10–14)
只检查文件是合法的 JSON 或 YAML:挡不住拼错的键和错误的类型
Sourcemeta JSON Schema CLI(集成或分发时):许可证为 AGPL-3.0,分发或作为服务提供前要按 27 审查;只在 CI 里独立运行时影响较小
YAML 检查yamllint · GPL-3.0(独立命令行工具)—
拿 yamllint 当 CI 编排的校验:它不懂 GitHub Actions 或 GitLab CI 的语义,见 CFG-036
JSON 与 YAML 格式化Prettier · MIT
Biome(TypeScript 项目已经用 Biome,JSON 交给它,YAML 仍用 Prettier)
同一种文件用两个格式器:互相改写
TOML 格式化与检查Tombi · Tombi、Taplo 均为 MIT
Taplo(存量项目已在用且没遇到问题;两者的配置不兼容,迁移要改配置)
用 YAML 或通用格式器处理 TOML:不懂 TOML 语法
Dockerfile 检查hadolint + docker build --check · hadolint 为 GPL-3.0(独立命令行工具)—
只 lint 不构建:证明不了能构建成功
SQL 检查与格式化SQLFluff · SQLFluff 为 MIT;pgFormatter 为 PostgreSQL License
pgFormatter(只用 PostgreSQL、只需要格式化,不需要 lint)
把 SQLFluff 当成 SQL 注入检查:它只看 SQL 文件本身,注入防护见 25
GitHub Actions 配置检查actionlint(语法)+ zizmor(安全) · actionlint、zizmor 均为 MIT
GitLab CI Lint(用 GitLab CI 时)
只跑 actionlint:它不查权限和注入这类安全问题
Shell 静态检查
ShellCheck · GPL-3.0(作为独立命令行工具在 CI 里运行;要随产品分发时见 27)
备选:bash -n(只想快速查语法错误;不能替代 ShellCheck)
不推荐:只靠人工评审
Shell 格式化
shfmt · BSD-3-Clause
Shell 脚本测试
bats-core · MIT
PowerShell 检查与测试
PSScriptAnalyzer(静态检查)+ Pester(测试) · PSScriptAnalyzer 为 MIT,Pester 为 Apache-2.0
不推荐:用 ShellCheck 查 .ps1
任务入口
just · just 为 CC0-1.0;Task 为 MIT;GNU Make 为 GPL-3.0 或更高版本
备选:Task(需要任务依赖、按输入输出跳过已完成的任务,或希望在 Windows 上不依赖 Bash 时);GNU Make(C/C++ 项目要按文件增量构建,或已有成熟的 Makefile);npm scripts(只有一个 Node 包的项目)
不推荐:一层套一层的包装脚本;为几条命令写复杂的 Makefile
JSON Schema 校验
check-jsonschema · check-jsonschema 为 Apache-2.0
备选:应用内的 JSON Schema 校验库(配置在程序启动时校验,各语言的校验库见 10–14)
不推荐:只检查文件是合法的 JSON 或 YAML;Sourcemeta JSON Schema CLI(集成或分发时)
YAML 检查
yamllint · GPL-3.0(独立命令行工具)
不推荐:拿 yamllint 当 CI 编排的校验
JSON 与 YAML 格式化
Prettier · MIT
备选:Biome(TypeScript 项目已经用 Biome,JSON 交给它,YAML 仍用 Prettier)
不推荐:同一种文件用两个格式器
TOML 格式化与检查
Tombi · Tombi、Taplo 均为 MIT
备选:Taplo(存量项目已在用且没遇到问题;两者的配置不兼容,迁移要改配置)
不推荐:用 YAML 或通用格式器处理 TOML
Dockerfile 检查
hadolint + docker build --check · hadolint 为 GPL-3.0(独立命令行工具)
不推荐:只 lint 不构建
SQL 检查与格式化
SQLFluff · SQLFluff 为 MIT;pgFormatter 为 PostgreSQL License
备选:pgFormatter(只用 PostgreSQL、只需要格式化,不需要 lint)
不推荐:把 SQLFluff 当成 SQL 注入检查
GitHub Actions 配置检查
actionlint(语法)+ zizmor(安全) · actionlint、zizmor 均为 MIT
备选:GitLab CI Lint(用 GitLab CI 时)
不推荐:只跑 actionlint
  • Shell 静态检查:仍在维护,代码仓库持续有提交,但正式发布间隔较长。
  • JSON Schema 校验:check-jsonschema 自带 GitHub workflow 等常见文件的 Schema,断网时要把用到的 Schema 放在本地。
  • TOML 格式化与检查:Taplo 的原作者已于 2025 年表示退出,现由一名维护者按自己的节奏维护,最近一次正式发布在 2025 年 5 月。Tombi 更新活跃,但项目较新、主要靠少数人维护,要定期复查。
  • Dockerfile 检查:镜像漏洞扫描和 SBOM 工具见 25。

常见错误

错误做法:把 set -e 当 try/catch,以为任何失败都会让脚本退出
应该:预期会失败的命令用 if 显式处理
错误做法:for f in $(ls) 和不加引号的变量
应该:加引号;用通配符或 find -print0
错误做法:curl ... | bash 安装工具
应该:下载固定版本并校验哈希,或用包管理器
错误做法:用 sed 或正则改 JSON、YAML
应该:用 jq、yq 或语言里的解析库,改完用 Schema 校验
错误做法:CI 里另写一套和本地不同的 lint、构建命令
应该:CI 调用仓库的任务入口
错误做法:check 任务里顺手跑了格式化
应该:check 只读,格式化放在 format
错误做法:Dockerfile 用 ARG 传 API 令牌
应该:用 BuildKit secret 挂载
错误做法:FROM xxx:latest 用于正式发布
应该:标签加 digest 固定,由机器人提 PR 升级
错误做法:把 .env 提交进仓库或打进镜像
应该:只提交 .env.example,运行时由平台注入
错误做法:所有配置都用 YAML,也不配 Schema
应该:按消费者选格式,机器读的配置配 Schema
错误做法:run: echo "${{ github.event.pull_request.title }}"
应该:先放进 env,再用 "$PR_TITLE" 引用
错误做法:用在线 cron 网站验证所有调度器的表达式
应该:用目标调度器自己的解析结果核对

待确认

下面的内容还没核实或待补调研,不要当成规则执行。

  • GitHub workflow 依赖锁文件的状态:官方文档写的是公开预览,有第三方报道称 2026-10-28 已正式发布;是否覆盖所有套餐、是否锁住间接依赖,说法不一致
  • GitHub Actions schedule 的 timezone 字段:已由官方更新日志确认,但更新日志的准确日期(03-19 或 03-26)和夏令时处理细节未从官方文档核实
  • 目标 sh(dash、BusyBox ash)对 set -o pipefail 的支持情况,未逐个核实
  • Tombi 能否完全替代 Taplo(配置迁移、编辑器支持、长期维护人数),需要跟踪;Biome 是否已改用 Tombi 处理 TOML 只见于第三方 PR 描述
  • Biome 是否已支持 YAML 格式化(影响 CFG-022 的分工),未核实
  • Docker 相关文档、PSScriptAnalyzer 文档沿用原调研的查阅结果,本轮未重新核实
  • 待补调研:工具链版本统一锁定的做法(mise、aqua 等)在脚本与配置工具上的用法,需与 10 的选型对齐
  • 待补调研:hadolint、yamllint、zizmor、SQLFluff 的推荐最小配置文件,以及每条硬约束对应的具体规则编号
  • 待补调研:Windows 下运行 just、Task 时用哪个 shell;GitLab CI 复用(extends、!reference、components)的写法
  • 待补调研:zsh 脚本是否需要单独的检查工具

来源

  1. S01 Google Shell Style Guide · Google · 社区 · 核实于 2026-11-13
  2. S02 GNU Bash Reference Manual: The Set Builtin · GNU · 官方文档 · 核实于 2026-11-13
  3. S03 ShellCheck · ShellCheck · 官方文档 · 核实于 2026-11-14
  4. S04 ShellCheck wiki: SC2086 · ShellCheck · 官方文档 · 核实于 2026-11-13
  5. S05 mvdan/sh (shfmt) · mvdan · 官方文档 · 核实于 2026-11-14
  6. S06 OWASP OS Command Injection Defense Cheat Sheet · OWASP · 标准 · 核实于 2026-11-14
  7. S07 PSScriptAnalyzer overview · Microsoft · 官方文档 · 核实于 2026-11-14
  8. S08 just · casey · 官方文档 · 核实于 2026-11-14
  9. S09 Task · go-task · 官方文档 · 核实于 2026-11-14
  10. S10 GNU Make Manual: Phony Targets · GNU · 官方文档 · 核实于 2026-11-14
  11. S11 JSON Schema 2020-12 · JSON Schema · 标准 · 核实于 2026-11-13
  12. S12 check-jsonschema · python-jsonschema · 官方文档 · 核实于 2026-11-14
  13. S13 YAML 1.2.2 Specification · YAML · 标准 · 2021-10-01 · 核实于 2026-11-13
  14. S14 RFC 8259: The JSON Data Interchange Format · IETF · 标准 · 2017-12 · 核实于 2026-11-14
  15. S15 yamllint · adrienverge · 官方文档 · 核实于 2026-11-14
  16. S16 yamllint: Rules · yamllint · 官方文档 · 核实于 2026-11-14
  17. S17 Prettier · Prettier · 官方文档 · 核实于 2026-11-14
  18. S18 Tombi · tombi-toml · 官方文档 · 核实于 2026-11-14
  19. S19 Taplo: The future of the project (issue #715) · Taplo · 官方文档 · 2024-12-20 · 核实于 2026-11-14
  20. S20 The Twelve-Factor App: Config · Twelve-Factor · 社区 · 核实于 2026-11-14
  21. S21 POSIX.1-2024: Environment Variables · The Open Group · 标准 · 2024 · 核实于 2026-11-14
  22. S22 Docker: Building best practices · Docker · 官方文档 · 核实于 2026-11-13
  23. S23 Docker: Build secrets · Docker · 官方文档 · 核实于 2026-11-13
  24. S24 Docker: Build checks · Docker · 官方文档 · 核实于 2026-11-13
  25. S25 hadolint · hadolint · 官方文档 · 核实于 2026-11-14
  26. S26 Docker: Multi-stage builds · Docker · 官方文档 · 核实于 2026-11-13
  27. S27 Docker: Build context and .dockerignore · Docker · 官方文档 · 核实于 2026-11-14
  28. S28 SQLFluff · SQLFluff · 官方文档 · 核实于 2026-11-14
  29. S29 SQLFluff: Rules reference · SQLFluff · 官方文档 · 核实于 2026-11-14
  30. S30 GitHub Actions: Secure use reference · GitHub · 官方文档 · 核实于 2026-11-13
  31. S31 GitHub Actions: Script injections · GitHub · 官方文档 · 核实于 2026-11-13
  32. S32 actionlint · rhysd · 官方文档 · 核实于 2026-11-14
  33. S33 zizmor · zizmor · 官方文档 · 核实于 2026-11-14
  34. S34 GitLab: CI Lint API · GitLab · 官方文档 · 核实于 2026-11-13
  35. S35 GitHub Actions: Workflow dependency lockfiles · GitHub · 官方文档 · 核实于 2026-11-14
  36. S36 GitHub Actions: Late March 2026 updates(schedule 支持时区) · GitHub · 官方文档 · 核实于 2026-11-14
  37. S37 crontab(5) — Linux manual page · man7.org · 官方文档 · 核实于 2026-11-13
  38. S38 Sourcemeta JSON Schema CLI · Sourcemeta · 官方文档 · 核实于 2026-11-14
  39. S39 pgFormatter · darold · 官方文档 · 核实于 2026-11-14
  40. S40 bats-core · bats-core · 官方文档 · 核实于 2026-11-14
  41. S41 Pester · Pester · 官方文档 · 核实于 2026-11-14
  42. S42 EditorConfig Specification · EditorConfig · 标准 · 核实于 2026-11-13