多步流水线 + HereDoc:一条 Shell 脚本实现全自动内容生产工作流
前两篇我们分别介绍了“单次调用”和“输出可解析”技巧,本篇将深入讲解如何把多个 Claude 调用串联起来,构建一条真正的自动化流水线,实现内容生产全流程脚本化。

HereDoc:优雅解决长 Prompt 的引号嵌套问题
当任务描述超过一行,且内部包含引号嵌套时,使用 -p "..." 方式极易出现引号冲突,阅读和维护都很痛苦:
# 引号嵌套导致难以阅读和维护claude -p "审查代码变更。规则:1. 检查类型声明;2. 禁止出现"TODO"标记;3. 检查"密钥"硬编码。" --allowed-tools "Read"
改用 HereDoc 方式则简洁清晰:
claude -p "$(cat <<'PROMPT'审查当前项目的代码变更。检查规则(按优先级):1. TypeScript 类型错误 —— 必须修复2. console.log 残留 —— 提交前全部删除3. 硬编码密钥/token —— 绝对禁止输出 JSON 数组:[{file, line, rule, issue, severity}]如果没有问题,输出空数组 []。只输出 JSON。PROMPT)" --allowed-tools "Bash,Read" --model haiku
语法非常直观:从 <<'标记' 开始,到单独一行的 标记 结束,中间内容原封不动传给命令。外层 $() 执行 cat 命令,结果自动填充到 -p 参数中。关键在于 'PROMPT' 的单引号,它能阻止 Shell 二次解析内部内容,确保提示词准确传递。
管道输入模式:任务指令也可以来自标准输入
之前的用法是“数据走管道,指令写 -p”。反过来同样可行——任务描述本身也可以通过管道传递:
# 以下三种写法效果完全一致# 1. 使用 -p 直接指定指令claude -p "把 Hello World 翻译成中文"# 2. 通过 echo 管道传递echo "把 Hello World 翻译成中文" | claude# 3. 使用 HereDoc 管道claude <<'EOF'读取 CLAUDE.md,列出所有关于代码质量的规则,按优先级排序。EOF
这种方式的优势在于,任务内容可以动态来自其他命令的输出。但有一个重要坑点:管道内容和 -p 不能同时使用——如果管道提供了内容,它会直接成为 prompt,-p 参数会被忽略。正确的做法是将动态内容手动拼接到 -p 字符串中:
# ❌ 错误:管道与 -p 混用,-p 被忽略cat ~/project/CLAUDE.md | claude -p "根据上面的规则审查代码"# ✅ 正确:通过变量将动态内容拼入 -pRULES=$(cat ~/project/CLAUDE.md)claude -p "根据以下规则审查代码。规则:$RULES" --allowed-tools "Read,Bash" --model haiku
多步流水线:将复杂任务拆解为可控步骤
单步完成一个复杂任务往往不够可靠——token 限制、中间结果无法审计、错误难以定位。拆分为多个步骤,每步结果落盘,整个过程清晰可控。
搜索资料列大纲写初稿去 AI 味┌──────────┐┌──────────┐┌──────────┐┌──────────┐│ claude -p││ claude -p││ claude -p││ claude -p││ WebSearch│───→│ --continue│───→│ --continue│───→│ --continue│└────┬─────┘└────┬─────┘└────┬─────┘└────┬─────┘ ▼ ▼ ▼ ▼research.jsonoutline.md draft.md final.md
数据传递有四种常用方式,根据场景灵活选择:
| 方式 | 写法 | 适用场景 | 注意事项 |
|---|---|---|---|
| 文件落盘 | > /tmp/result.md,下一步 $(cat ...) 读取 |
数据量大、需要审计追溯 | 安全可靠,强烈推荐 |
$() 嵌入 |
RESULT=$(cat file); claude -p "...$RESULT" |
数据量较小 | 需与 -p 配合使用 |
--continue |
下一步加 --continue "$SID" |
保持会话上下文 | 第一步必须先用 --session-id 创建 |
| 管道 | echo "任务" | claude |
单步简单任务 | ⚠️ 管道内容会覆盖 -p,两者不可混用 |
完整示例:文章自动生产线
#!/bin/bash# 全自动内容生产流水线:搜索资料 → 列写大纲 → 生成初稿 → 去除 AI 痕迹TOPIC="Claude Code Hooks 入门"SID="article-$(date +%s)" # 唯一会话 ID,所有步骤共享上下文# 第一步:搜索相关资料(--session-id 命名该会话)echo "=== 开始搜索资料 ==="claude -p "搜索「$TOPIC」的 3 篇高质量文章。输出标题、URL、核心观点。JSON 格式。" --session-id "$SID" --allowed-tools "WebSearch" --model haiku > /tmp/step1-research.json# 第二步:生成文章大纲(--continue 延续上一步上下文)echo "=== 开始列写大纲 ==="RESEARCH=$(cat /tmp/step1-research.json)claude -p "根据以下资料,为「$TOPIC」生成文章大纲。结构包含:引言 + 3 个核心要点 + 总结。n资料:$RESEARCH" --continue "$SID" --allowed-tools "Read" --model haiku > /tmp/step2-outline.md# 第三步:撰写初稿echo "=== 开始撰写初稿 ==="OUTLINE=$(cat /tmp/step2-outline.md)claude -p "按以下提纲写一篇 1500 字的技术文章。要求使用短句,用通俗语言解释技术概念。n提纲:$OUTLINE" --continue "$SID" --allowed-tools "Read" > /tmp/step3-draft.md# 第四步:去除 AI 味(润色)echo "=== 开始去除 AI 痕迹 ==="DRAFT=$(cat /tmp/step3-draft.md)claude -p "请去掉以下文章中「值得注意的是」「综上所述」等 AI 套话,将长句拆成短句,被动语态改为主动语态。n文章:$DRAFT" --continue "$SID" --allowed-tools "Read" > /tmp/step4-final.mdecho "✅ 流水线执行完毕,最终文件:/tmp/step4-final.md"
每步的数据流转关系如下:
| 步骤 | Claude 接收到的内容 | 数据传递方式 |
|---|---|---|
| 搜索 | 搜索指令 + 主题 | — |
| 大纲 | 大纲指令 + 上一步的搜索结果 | $() 拼入 -p 字符串 |
| 初稿 | 写作指令 + 上一步的大纲 | $() 拼入 -p 字符串 |
| 去 AI 味 | 润色指令 + 上一步的初稿 | $() 拼入 -p 字符串 |
| 所有步骤 | 前面所有对话的完整上下文 | --continue "$SID" |
直接运行即可:
chmod +x ~/scripts/auto-article.sh~/scripts/auto-article.sh
Shell 函数封装:将长命令缩成一个词
虽然脚本已写好,但距离“随手就用”还有一步——把常用命令封装成简短函数。
# 将以下内容添加到 ~/.zshrc 中ask() { claude -p "$*" --allowed-tools "Read,WebSearch,WebFetch" --模型 haiku; }review() {local files=$(git diff HEAD --name-only 2>/dev/null | tr 'n' ' ')[ -z "$files" ] && { echo "无变更文件"; return; }claude -p "审查以下文件的变更,列出最严重的 3 个问题:$files" --allowed-tools "Bash,Read" --模型 haiku}
之后即可在终端中直接使用:
ask React Concurrent Mode 是什么review # 在当前仓库任意目录执行
这里有个关键点:必须使用函数 () {},而不能用 alias。因为 $(git diff ...) 需要在每次调用时动态执行,alias 在定义时就会固定结果,只有函数才能保证每次执行都实时计算当前变更。
