1. 运维脚本越写越乱,怎么用统一 API 通道批量格式化 shell 脚本
手里维护过几十上百个 shell 脚本的人,大概率都遇到过这种场面:同一个仓库里,有人用 4 空格缩进,有人用 Tab,if和then挤在一行,函数之间空行数量全靠心情。单看一个脚本还能忍,一旦要做代码评审、交接或者批量改逻辑,光是缩进和风格差异就能把 diff 撑到几百行,真正改动的业务逻辑反而被淹没。
shell 脚本格式化这件事本身不复杂,shfmt、shellcheck这类工具已经很成熟,编辑器插件也能一键处理。但现实问题是:脚本散落在不同机器、不同目录,有的在跳板机上,有的在 CI 流水线里,你不可能给每台机器都装一遍插件、配一遍路径。更麻烦的是,格式化只能解决缩进和对齐,像变量命名不统一、缺少set -euo pipefail、错误处理缺失这类「风格 + 规范」问题,纯靠格式化工具搞不定,得靠模型理解上下文来给建议。
我试过的思路是:把「机械格式化」交给shfmt,把「风格审查和改写建议」交给大模型,而调用模型这件事统一走一个 API 通道,避免每个脚本、每台机器都去维护不同的 Key 和 Base URL。这篇就按这个思路,演示怎么用 TaoToken 的统一 API 通道,对一批 shell 脚本做批量格式化与风格校验,给出可复制的格式化脚本、接入配置,以及前后对比的验证步骤,目标是一次跑通、结果可复核。
适合谁看:日常要维护一批 shell 脚本的运维、SRE、后端开发;正在做脚本规范化治理、想把风格检查接进 CI 的人;以及手头有多个模型 Key、想统一管理调用入口的开发者。核心检索词就是 shell 脚本格式化、批量整理缩进与风格,下面所有步骤都围绕它展开。
先说清楚整体链路,避免你看到一半不知道在干嘛。整条链路分两段:第一段是本地确定性处理,用shfmt把缩进、换行、对齐这些机械问题一次性抹平,这一步不依赖网络,结果完全可复现;第二段是模型审查,把格式化后的脚本内容发给模型,让它按你给定的风格规则输出问题清单或改写建议,这一步通过 TaoToken 的统一 API 通道调用,Key 和 Base URL 只配一次。两段之间用一个小脚本串起来,遍历目录、逐个处理、输出报告。
为什么要拆成两段而不是全丢给模型?因为缩进这种确定性问题,模型反而不如shfmt稳定,同样的输入模型可能这次给你 4 空格下次给你 2 空格,而shfmt是确定性的。模型的价值在于「理解语义后的风格判断」,比如它能看到rm -rf $DIR/这种没加引号、没做空值保护的写法,提醒你补上"${DIR:?}"。把两者结合,才是既稳定又聪明的方案。
2. TaoToken 统一 API 通道前置准备:Key、Base URL 与模型 ID
在写格式化脚本之前,先把调用通道准备好。TaoToken 在这里扮演的角色是「统一入口」:你不需要在每台机器上分别配置不同厂商的 Key,也不用记一堆不同的 Base URL,只要拿到一个 Key,配一个 Base URL,选一个 Model ID,就能在脚本里稳定调用模型。官网入口是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 地址是 https://taotoken.net/api ,注意 API 地址后面不加任何查询参数。
第一步是拿 Key。登录后进入控制台,在 API Keys 页面创建一个新的 Key。建议给这个 Key 起个能看出用途的名字,比如shell-format-batch,方便以后排查是哪个脚本在调用。创建完立刻复制保存,页面刷新后就看不到完整 Key 了。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Keys 页面是 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
第二步是确认 Base URL 和模型 ID。Base URL 固定用https://taotoken.net/api,模型 ID 按你实际要用的填,比如做代码审查和改写建议,选一个擅长代码的模型即可。这里不编造具体价格和评测数据,你以控制台里实际可选的模型列表为准。如果你后面要长期跑批量任务、甚至接 Agent 做自动化,可以了解下 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。
第三步是把这些配置落到环境变量里,而不是硬编码进脚本。这样脚本可以进 Git,Key 不会泄露。在~/.bashrc或~/.zshrc里加:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api" export TAOTOKEN_MODEL="你的模型ID"改完执行source ~/.bashrc生效。验证环境变量是否读到:
echo "$TAOTOKEN_BASE_URL" echo "${TAOTOKEN_API_KEY:0:6}****"第二行只打印 Key 前 6 位,避免完整 Key 出现在终端历史里。这一步看着简单,但后面所有脚本都依赖这三个变量,配错了会直接导致 401 或连接失败,所以先确认再往下走。
如果你用的是 Claude Code 这类工具做脚本润色,接入方式也是同一套:Base URL 填https://taotoken.net/api,Key 填你创建的 Key,Model ID 填对应模型。Claude Code 的接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有具体的配置字段说明。这里强调一点:Base URL、Key、Model ID 这三件套必须同时正确,缺一个都会失败,后面排障章节会针对每种失败给对照。
3. 可复制的批量格式化脚本与 TaoToken 接入配置
这一节是核心,给出能直接跑的脚本。整体分三个文件:一个shfmt格式化脚本、一个调用模型的审查脚本、一个串联的入口脚本。先建目录:
mkdir -p ~/shell-format/{scripts,formatted,reports}把你要处理的脚本放进~/shell-format/scripts。先确认shfmt装了没:
shfmt --version没有的话按你的系统装,macOS 用brew install shfmt,Linux 可以直接下二进制。装好后先做纯格式化,脚本format.sh:
#!/usr/bin/env bash set -euo pipefail SRC_DIR="${1:-$HOME/shell-format/scripts}" OUT_DIR="${2:-$HOME/shell-format/formatted}" mkdir -p "$OUT_DIR" find "$SRC_DIR" -type f -name "*.sh" | while read -r f; do rel="${f#"$SRC_DIR"/}" out="$OUT_DIR/$rel" mkdir -p "$(dirname "$out")" # -i 2 用2空格缩进,-ci 缩进case分支,-sr 重定向空格 shfmt -i 2 -ci -sr -w "$f" 2>/dev/null || true cp "$f" "$out" echo "formatted: $rel" done注意这里-w是直接改写原文件,如果你不想动原文件,把-w "$f"改成shfmt -i 2 -ci -sr "$f" > "$out",只输出到 formatted 目录。参数含义:-i 2指定 2 空格缩进,-ci让case分支也缩进,-sr在重定向符号周围加空格。这三个参数基本能覆盖大部分团队的风格约定,你可以按自己团队规范调整。
接下来是调用模型的审查脚本review.sh,它读取格式化后的脚本,发给 TaoToken 统一通道,拿回风格问题清单:
#!/usr/bin/env bash set -euo pipefail FILE="$1" REPORT_DIR="${2:-$HOME/shell-format/reports}" mkdir -p "$REPORT_DIR" : "${TAOTOKEN_API_KEY:?请先设置 TAOTOKEN_API_KEY}" : "${TAOTOKEN_BASE_URL:?请先设置 TAOTOKEN_BASE_URL}" : "${TAOTOKEN_MODEL:?请先设置 TAOTOKEN_MODEL}" CONTENT="$(cat "$FILE")" PROMPT="你是一名 shell 脚本规范审查员。请审查下面的脚本,只输出问题清单,每条格式为:行号范围 | 问题类型 | 修改建议。重点关注:变量引用是否加引号、是否缺少 set -euo pipefail、错误处理、危险命令保护。不要重写整个脚本。\n\n脚本内容:\n$CONTENT" PAYLOAD="$(jq -n \ --arg model "$TAOTOKEN_MODEL" \ --arg prompt "$PROMPT" \ '{model: $model, messages: [{role: "user", content: $prompt}], temperature: 0.2}')" curl -sS "$TAOTOKEN_BASE_URL/v1/chat/completions" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" \ | jq -r '.choices[0].message.content' \ > "$REPORT_DIR/$(basename "$FILE").review.txt" echo "reviewed: $(basename "$FILE")"这里用jq构造 JSON,避免手拼字符串导致转义错误。temperature设成 0.2,让输出更稳定、更贴近规则而不是自由发挥。请求路径是/v1/chat/completions,这是 OpenAI 兼容格式,Base URL 拼上路径就是完整地址。
最后是入口脚本run-all.sh,把两段串起来:
#!/usr/bin/env bash set -euo pipefail BASE="$HOME/shell-format" bash "$BASE/format.sh" "$BASE/scripts" "$BASE/formatted" find "$BASE/formatted" -type f -name "*.sh" | while read -r f; do bash "$BASE/review.sh" "$f" "$BASE/reports" done echo "全部完成,报告在 $BASE/reports"如果你更习惯用配置文件而不是环境变量,也可以写一个settings.json风格的配置,把三件套集中管理,路径和字段如下:
{ "base_url": "https://taotoken.net/api", "api_key_env": "TAOTOKEN_API_KEY", "model": "你的模型ID", "format": { "indent": 2, "case_indent": true, "space_redirect": true } }脚本里读这个 JSON 也行,但环境变量方式更简单,推荐先用环境变量跑通。注意base_url字段就是https://taotoken.net/api,不要多加/v1,路径在请求时再拼。
4. 验证请求与成功结果:前后对比与报告复核
脚本写完,先拿一个脚本试跑,别一上来就全量。准备一个故意写得很乱的测试脚本~/shell-format/scripts/demo.sh:
#!/bin/bash foo=1 if [ $foo -eq 1 ];then echo "yes" rm -rf $HOME/tmp/ fi这个脚本问题很明显:if和then挤一起、缩进混乱、$foo没加引号、rm -rf没做保护。先跑格式化:
bash ~/shell-format/format.sh ~/shell-format/scripts ~/shell-format/formatted cat ~/shell-format/formatted/demo.sh格式化后应该变成:
#!/bin/bash foo=1 if [ $foo -eq 1 ]; then echo "yes" rm -rf $HOME/tmp/ fi缩进统一成 2 空格,then前加了空格,结构清晰了。但注意,shfmt不会帮你加引号、不会帮你加set -euo pipefail,这些正是模型审查要补的。接着跑审查:
bash ~/shell-format/review.sh ~/shell-format/formatted/demo.sh cat ~/shell-format/reports/demo.sh.review.txt成功的话,报告里会出现类似这样的条目:
3 | 变量引用未加引号 | 建议改为 if [ "$foo" -eq 1 ]; then 5 | 危险命令缺少保护 | rm -rf 前建议校验变量非空,如 "${HOME:?}" 1 | 缺少严格模式 | 建议在 shebang 后加 set -euo pipefail这就是「机械格式化 + 语义审查」的组合效果:缩进这种确定性问题由shfmt保证,风格和安全隐患由模型指出。你可以把报告和格式化后的文件一起提交,评审时 diff 干净,问题清单也一目了然。
验证请求是否真的走通了 TaoToken 通道,可以单独发一个最小请求:
curl -sS "$TAOTOKEN_BASE_URL/v1/chat/completions" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"$TAOTOKEN_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"回复ok\"}]}" \ | jq -r '.choices[0].message.content'返回ok就说明 Key、Base URL、Model ID 三件套都对。如果这一步就失败,先别怀疑脚本,去排障章节对照报错。想直接在网页里验证模型是否可用,可以用模型对话页面 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 手动发一条消息,确认通道正常后再回到脚本。
批量跑完后,复核方式建议这样:先看formatted目录里文件数量是否和scripts一致,再看reports目录里每个脚本是否都有对应报告,最后抽查两三个报告内容是否合理。命令:
ls ~/shell-format/scripts/*.sh | wc -l ls ~/shell-format/formatted/*.sh | wc -l ls ~/shell-format/reports/*.review.txt | wc -l三个数字应该一致。不一致说明某个环节漏了文件,通常是find的匹配规则或子目录处理问题。
5. 本篇常见报错排查:401、local proxy failed、reading choices、OAuth
批量跑脚本时最容易卡在调用环节,这里把几个高频报错和对照处理列清楚,遇到时直接对号入座。
401 Unauthorized。最常见,原因是 Key 不对或没读到。先确认环境变量:
echo "${TAOTOKEN_API_KEY:0:6}****"如果打印为空,说明变量没设置或没source。如果 Key 有值还报 401,检查是不是复制时带了空格或换行,重新创建 Key 再试。另外确认请求头是Authorization: Bearer $TAOTOKEN_API_KEY,Bearer后面有一个空格,少了空格也会 401。
local proxy failed / connection refused。这类报错通常是 Base URL 写错或网络层问题。确认TAOTOKEN_BASE_URL是https://taotoken.net/api,不要写成带/v1的完整路径再拼一次,也不要在末尾加斜杠导致路径变成//v1。检查方式:
curl -sS -o /dev/null -w "%{http_code}\n" "$TAOTOKEN_BASE_URL/v1/chat/completions" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"$TAOTOKEN_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}"返回 200 说明通道正常,返回 000 说明连不上,先排查本机网络和 DNS。
reading choices 相关报错 / cannot read property 'choices'。这通常不是网络问题,而是返回体结构和你预期不一致。原因可能是:请求体 JSON 格式错误导致服务端返回了错误对象,或者模型 ID 不存在。用jq看完整返回:
curl -sS "$TAOTOKEN_BASE_URL/v1/chat/completions" \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD" | jq .如果返回里有error字段,按里面的 message 处理。常见是模型 ID 拼错,或者messages数组格式不对。脚本里用jq -n构造就是为了避免这种手拼错误。
OAuth / 认证方式不匹配。如果你用的是 Claude Code 或类似工具,报 OAuth 相关错误,说明工具默认走了它自己的登录流程,而你要用的是 API Key 方式。这时候需要在工具的配置里显式指定 Base URL、Key、Model ID 三件套,关掉它默认的 OAuth 登录。Claude Code 的具体字段在接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里有说明,照着填即可。记住三件套缺一不可:Base URL 是https://taotoken.net/api,Key 是你创建的 Key,Model ID 是控制台里可选的模型。
报告为空但没报错。脚本跑完reports目录里文件是空的,通常是模型返回内容被jq -r '.choices[0].message.content'取出来是 null。先看原始返回,确认choices数组非空。也可能是 prompt 太长被截断,把脚本内容分块发送即可。
排障时建议把curl的-sS保留,-S会在出错时显示错误信息,比静默失败好定位。另外把每次请求的原始返回存一份到reports/raw目录,出问题时能回溯。
6. 把批量格式化接进日常:从手动到流水线
跑通单次批量之后,下一步是让它变成日常习惯,而不是每次手动敲命令。最直接的做法是把这个入口脚本挂到 Git 的 pre-commit 钩子里,提交前自动格式化改动的脚本并生成审查报告。钩子内容大致是:
#!/usr/bin/env bash set -euo pipefail changed=$(git diff --cached --name-only --diff-filter=ACM | grep '\.sh$' || true) [ -z "$changed" ] && exit 0 for f in $changed; do shfmt -i 2 -ci -sr -w "$f" bash ~/shell-format/review.sh "$f" ~/shell-format/reports done这样每次提交 shell 脚本,缩进自动统一,审查报告自动生成,评审时只看报告里的问题清单就行。注意钩子里只格式化暂存区的文件,不要全量跑,否则大仓库会很慢。
如果团队用 CI,可以把审查脚本放进流水线的一个 job,对 PR 里改动的脚本跑一遍,把报告作为构建产物上传。这样风格问题在合并前就暴露,不用等到人工评审。长期跑这类批量任务、或者想接 Agent 做自动化处理的,可以看下 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它更适合持续性的编码和 Agent 场景。
几个实用技巧,都是踩过的坑换来的。第一,shfmt的缩进参数要和团队约定一致,别一个仓库两种缩进,-i 2和-i 4选一个定死。第二,模型审查的 prompt 里明确「只输出问题清单,不要重写整个脚本」,否则模型容易给你返回一整份改写后的脚本,diff 反而更大。第三,报告文件按脚本路径命名,别用时间戳,这样同一个脚本的历史报告可以对比。第四,Key 只放环境变量,脚本进 Git 前检查一遍有没有硬编码。
最后说下验证闭环。每次批量跑完,除了看文件数量一致,建议再抽查一个脚本,手动对比格式化前后的 diff:
diff -u ~/shell-format/scripts/demo.sh ~/shell-format/formatted/demo.shdiff 里应该只有缩进和空格变化,没有逻辑改动。如果发现逻辑被改了,说明shfmt参数或脚本处理有问题,立刻停下来排查。审查报告则用来确认模型指出的问题是否真实存在,别盲目全信,模型也会误报,尤其是对某些特殊写法的脚本。
整套流程跑顺之后,你会发现 shell 脚本格式化这件事从「每次手动开编辑器插件」变成了「提交时自动完成」,缩进和风格不再靠人盯,模型负责提醒那些格式化工具看不到的规范问题。通道统一在 TaoToken 上,Key 和 Base URL 只配一次,换机器、换项目都不用重新折腾。需要新建 Key 或查看模型列表时,去 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 操作即可。