<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Tony Bai &#187; Bash</title>
	<atom:link href="http://tonybai.com/tag/bash/feed/" rel="self" type="application/rss+xml" />
	<link>https://tonybai.com</link>
	<description>一个程序员的心路历程</description>
	<lastBuildDate>Wed, 08 Apr 2026 00:17:11 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>极简主义的胜利：OpenClaw 核心引擎 Pi 的架构哲学与开发实录</title>
		<link>https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism/</link>
		<comments>https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism/#comments</comments>
		<pubDate>Sun, 15 Feb 2026 00:02:14 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgentCore]]></category>
		<category><![CDATA[AINativeApplications]]></category>
		<category><![CDATA[AI原生应用]]></category>
		<category><![CDATA[ArchitecturePhilosophy]]></category>
		<category><![CDATA[AtomicTools]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[benchmark]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[CodingAgent]]></category>
		<category><![CDATA[ContextEngineering]]></category>
		<category><![CDATA[ContextHandoff]]></category>
		<category><![CDATA[DifferentialRendering]]></category>
		<category><![CDATA[LLMAPI]]></category>
		<category><![CDATA[LLM接口]]></category>
		<category><![CDATA[Minimalism]]></category>
		<category><![CDATA[openclaw]]></category>
		<category><![CDATA[Pi]]></category>
		<category><![CDATA[pi-agent-core]]></category>
		<category><![CDATA[pi-ai]]></category>
		<category><![CDATA[pi-tui]]></category>
		<category><![CDATA[ProgressiveDisclosure]]></category>
		<category><![CDATA[RetainedMode]]></category>
		<category><![CDATA[SoftwareEngineering]]></category>
		<category><![CDATA[tmux]]></category>
		<category><![CDATA[TUI]]></category>
		<category><![CDATA[YOLOMode]]></category>
		<category><![CDATA[YOLO模式]]></category>
		<category><![CDATA[上下文工程]]></category>
		<category><![CDATA[保留模式]]></category>
		<category><![CDATA[原子工具]]></category>
		<category><![CDATA[基准测试]]></category>
		<category><![CDATA[差异化渲染]]></category>
		<category><![CDATA[智能体内核]]></category>
		<category><![CDATA[极简主义]]></category>
		<category><![CDATA[架构哲学]]></category>
		<category><![CDATA[渐进式披露]]></category>
		<category><![CDATA[终端UI]]></category>
		<category><![CDATA[编程智能体]]></category>
		<category><![CDATA[跨提供商切换]]></category>
		<category><![CDATA[软件工程]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5895</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism 大家好，我是Tony Bai。 在 AI 辅助编程工具（Coding Agent）日益臃肿的今天，我们是否走偏了方向？ 过去的两年里，我们见证了从 ChatGPT 复制粘贴，到 Copilot 自动补全，再到 Cursor 和 Claude Code 这种全自动 Agent 的演进。然而，随着功能的堆砌，工具变得越来越“重”。Claude Code 从一个轻量级的 CLI 变成了一个充满 80% 我们不需要功能的“宇宙飞船”，系统提示词（System Prompt）在每次更新中剧烈变动，甚至导致模型行为不可预测。 作为 OpenClaw 的核心智能体，Pi 的诞生源于一种“反叛”精神：如果我不需要它，我就不会构建它。 本文将基于 Pi 作者的深度复盘，剖析如何构建一个极简、可控、且在基准测试中击败主流竞品的 Coding Agent。你可以将之看成一份关于 AI 原生应用架构设计的教科书。 回归原点——为什么要重新造轮子？ 在决定构建 Pi 之前，作者尝试了市面上几乎所有的 Agent Harness（智能体框架），包括 Claude Code, Codex, Amp等。 现有工具的“三大原罪” 不可控的上下文（Context）：现有的框架往往在背后注入大量并未在 UI 中展示的 Prompt。对于 Coding [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/openclaw-core-engine-pi-architecture-philosophy-minimalism-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism">本文永久链接</a> &#8211; https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism</p>
<p>大家好，我是Tony Bai。</p>
<p>在 AI 辅助编程工具（Coding Agent）日益臃肿的今天，我们是否走偏了方向？</p>
<p>过去的两年里，我们见证了从 ChatGPT 复制粘贴，到 Copilot 自动补全，再到 Cursor 和 <a href="http://gk.link/a/12EPd">Claude Code</a> 这种全自动 Agent 的演进。然而，随着功能的堆砌，工具变得越来越“重”。Claude Code 从一个轻量级的 CLI 变成了一个充满 80% 我们不需要功能的“宇宙飞船”，系统提示词（System Prompt）在每次更新中剧烈变动，甚至导致模型行为不可预测。</p>
<p>作为 OpenClaw 的核心智能体，<a href="https://github.com/badlogic/pi-mono">Pi 的诞生</a>源于一种“反叛”精神：如果我不需要它，我就不会构建它。</p>
<p>本文将基于 <a href="https://mariozechner.at/posts/2025-11-30-pi-coding-agent/">Pi 作者的深度复盘</a>，剖析如何构建一个极简、可控、且在基准测试中击败主流竞品的 Coding Agent。你可以将之看成一份关于 AI 原生应用架构设计的教科书。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/ai-app-dev-primer-qr.png" alt="" /></p>
<h2>回归原点——为什么要重新造轮子？</h2>
<p>在决定构建 Pi 之前，作者尝试了市面上几乎所有的 Agent Harness（智能体框架），包括 Claude Code, Codex, <a href="https://tonybai.com/2026/02/09/amp-kills-vscode-plugin-human-ai-pair-programming-is-dead/">Amp</a>等。</p>
<h3>现有工具的“三大原罪”</h3>
<ul>
<li>不可控的上下文（Context）：现有的框架往往在背后注入大量并未在 UI 中展示的 Prompt。对于 Coding Agent 来说，上下文工程（Context Engineering）是核心。如果开发者无法精确控制输入模型的每一个 Token，就无法获得稳定的输出。</li>
<li>糟糕的调试体验与黑盒：大多数框架不允许开发者检查每一次交互的细节。当 Agent 犯错时，你不知道是 Prompt 的问题，还是模型的问题。</li>
<li>自托管（Self-hosting）的噩梦：许多框架（如 OpenCode）依赖 Vercel AI SDK，这在处理自托管模型（如 Ollama, vLLM）的工具调用（Tool Calling）时经常出现兼容性问题。</li>
</ul>
<h3>Pi 的设计哲学</h3>
<p>Pi 的核心理念是：Opinionated and Minimal（固执且极简）。</p>
<p>它不是为了服务百万用户而设计的通用产品，而是为了满足硬核开发者需求而生的“瑞士军刀”。为了实现这一目标，Pi 被拆解为四个核心模块：</p>
<ul>
<li>pi-ai: 一个统一的 LLM API 抽象层。</li>
<li>pi-agent-core: 智能体循环与事件流处理。</li>
<li>pi-tui: 一个基于差异化渲染的极简终端 UI 框架。</li>
<li>pi-coding-agent: 将上述组件串联起来的 CLI。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/openclaw-core-engine-pi-architecture-philosophy-minimalism-2.png" alt="" /></p>
<h2>驯服多模型世界的“巴别塔” —— pi-ai</h2>
<p>构建 Agent 的第一步是解决模型调用的碎片化问题。虽然市面上看似只有四家主流 API（OpenAI, Anthropic, Google, xAI），但在实际工程落地中，细节充满了魔鬼。</p>
<h3>API 的“方言”问题</h3>
<p>尽管大家都声称兼容 OpenAI 格式，但各家的理解千差万别：</p>
<ul>
<li>Reasoning 字段的混乱：OpenAI 不支持在 Completions API 中返回推理过程，而 DeepSeek 等推理模型则在各自的字段中返回（有的叫 reasoning_content，有的叫 reasoning）。</li>
<li>参数的不兼容：Cerebras 和 xAI 不支持 store 字段；Mistral 使用 max_tokens 而不是 max_completion_tokens；Grok 不支持 reasoning_effort。</li>
</ul>
<p>pi-ai 建立了一个健壮的适配层，通过详尽的测试套件（覆盖图像输入、推理追踪、工具调用）来抹平这些差异。</p>
<h3>真正的上下文无缝切换（Context Handoff）</h3>
<p>这是一个极具创新性的功能。在开发过程中，我们经常需要切换模型（例如：用便宜的模型做推理，用昂贵的模型写代码）。</p>
<p>然而，不同提供商对“工具调用”和“思维链”的格式定义完全不同。如果中途从 Claude 切换到 OpenAI，上下文往往会崩溃。</p>
<p>pi-ai 实现了<strong>跨提供商的上下文序列化与反序列化</strong>。</p>
<ul>
<li>它将 Anthropic 的
<thinking> 标签转换为 OpenAI 能够理解的内容块。</li>
<li>它处理了提供商特有的签名 Blob 数据，确保在切换模型后，对话历史依然连贯。</li>
</ul>
<p>这意味着你可以用 Claude Sonnet 进行规划，然后无缝切换到 GPT-5 Codex 进行代码生成，最后序列化保存到 JSON 中以备后用。</p>
<h3>被遗忘的“中止信号”</h3>
<p>许多 LLM SDK 忽略了 AbortController 的支持。在生产环境中，能够随时打断 Agent 的胡言乱语是至关重要的。pi-ai 从底层支持了全链路的中止信号，不仅能停止文本生成，还能停止正在进行的工具调用。</p>
<h3>结构化的工具结果</h3>
<p>传统的 Agent 框架往往直接将工具的文本输出扔给 LLM。但在 UI 层面，用户需要看到更丰富的信息（如图片、图表）。</p>
<p>Pi 引入了“分离式工具结果”设计：</p>
<ul>
<li>给 LLM 看的：纯文本或 JSON。</li>
<li>给 UI 看的：结构化数据或 Base64 图片。</li>
</ul>
<p>例如，一个天气工具可以给 LLM 返回“东京 25度”，同时给 UI 返回一个包含温度趋势图的 JSON 对象供渲染。</p>
<h2>重新发明终端 UI —— pi-tui</h2>
<p>为什么一个 Agent 项目要自己写一个 UI 框架？作者给出的理由非常硬核：现有的 TUI 库（如 Ink, Blessed）要么太重（像写 React），要么已停止维护。</p>
<h3>TUI 的两种流派</h3>
<ul>
<li>全屏接管模式（Full Screen）：像 Vim 一样接管整个视口。缺点是失去了终端原生的滚动条和搜索功能。</li>
<li>线性追加模式（Linear Append）：像标准 CLI 一样追加输出，只在需要时回溯光标更新内容。这是 Claude Code 和 Pi 选择的路线。</li>
</ul>
<h3>差异化渲染</h3>
<p>为了在不使用 React 这种重型 Virtual DOM 的情况下实现无闪烁更新，Pi 实现了一个基于 Retained Mode（保留模式）的渲染引擎。</p>
<ul>
<li>组件缓存：每个组件（如消息框、输入框）缓存其渲染结果。如果内容未变，直接复用。</li>
<li>双缓冲技术：维护一个“后备缓冲区（Backbuffer）”，记录屏幕上当前显示的内容。</li>
<li>最小化重绘：每次更新时，仅重绘发生变化的行。</li>
</ul>
<p>这种极致的优化使得 Pi 在 Ghostty 或 iTerm2 等现代终端中实现了丝滑的、近乎零闪烁的体验，同时内存占用极低（仅几百 KB）。</p>
<h2>极简主义的智能体设计 —— Less is More</h2>
<p>这是 Pi 最具争议也最具启发性的部分。它彻底抛弃了业界流行的“最佳实践”，走出了一条极其精简的道路。</p>
<h3>System Prompt：1000 Token 足矣</h3>
<p>与 Claude Code 动辄上万 Token 的 System Prompt 不同，Pi 的 Prompt 加起来不到 1000 Token。</p>
<p>现在的 Frontier Models（前沿模型）已经经过了大量的 RL（强化学习）训练，它们天生就懂如何写代码。你不需要教它“你是一个资深的工程师”，你只需要给它工具。</p>
<h3>工具集：只要这 4 个就够了</h3>
<p>Pi 没有为每种操作都封装专门的工具（如 create_file, delete_file, search_code），而是回归了 Unix 哲学。</p>
<p>它只提供了 4 个原子工具：</p>
<ul>
<li>read: 读取文件。</li>
<li>write: 覆盖/创建文件。</li>
<li>edit: 基于字符串匹配的精确修改（Surgical edits）。</li>
<li>bash: 执行任意 Shell 命令。</li>
</ul>
<p>模型非常擅长使用 Bash。为什么要封装一个 ls 工具？直接让模型运行 ls -la 就好了。为什么要封装 grep？模型自己会写 grep 命令。这种设计不仅减少了 Token 消耗，还赋予了 Agent 无限的灵活性。</p>
<h3>安全哲学：YOLO 模式 (You Only Look Once)</h3>
<p>现在的 Coding Agent 充斥着“安全剧场（Security Theater）”。它们试图拦截每一个文件读写操作，或者限制网络访问。</p>
<p>但作者指出：一旦你允许 Agent 写代码并运行代码，游戏就结束了。Agent 完全可以写一段 Python 脚本来绕过所有的文件系统沙箱。</p>
<p>Pi 的选择是：完全信任（Full Trust）。</p>
<ul>
<li>没有权限拦截。</li>
<li>没有命令预检查。</li>
<li>完整的网络和文件系统访问权限。</li>
</ul>
<p>与其做无用的防御，不如让开发者在隔离环境（如容器或虚拟机）中运行 Agent。</p>
<h3>拒绝“过度工程化”</h3>
<ul>
<li>No Built-in To-dos: 任务列表应该存在于 TODO.md 文件中，而不是 Agent 的内存里。文件是最好的持久化。</li>
<li>No Plan Mode: 所谓的“规划模式”往往限制了 Agent 的灵活性。Pi 鼓励通过对话和 Markdown 文件（PLAN.md）来进行持久化的规划。</li>
<li>No MCP Support: 作者认为 MCP（Model Context Protocol）对于大多数用例来说是“杀鸡用牛刀”。像 Playwright MCP 这种服务，一上来就往上下文里塞 13k Token 的工具描述，极其浪费。Pi 的替代方案是：CLI 工具 + README。Agent 需要用什么工具，就读那个工具的 README，然后用 Bash 调用。这是最自然的渐进式披露（Progressive Disclosure）。</li>
</ul>
<h3>放弃后台 Bash，拥抱 tmux</h3>
<p>Claude Code 试图在后台管理耗时的进程（如开发服务器），但处理得并不好，且缺乏可观测性。</p>
<p>Pi 的解决方案极其极客：使用 tmux。</p>
<p>如果 Agent 需要运行一个长时间的 Server 或调试器（LLDB），它会直接在 tmux 会话中启动。用户可以随时 Attach 到这个会话中查看日志、接管调试。这是最高级的可观测性。</p>
<h2>实战效果与基准测试</h2>
<p>这种“简陋”的架构真的行吗？数据说明了一切。</p>
<p>在 Terminal-Bench 2.0 基准测试中，使用 Claude Opus 4.5 的 Pi Agent：</p>
<ul>
<li>排名第 7，仅次于经过重度优化的 Codex CLI 和商业化产品 Warp。</li>
<li>击败了 OpenHands, SWE-Agent 等著名的开源 Agent 框架。</li>
<li>准确率达到 49.8%，与排名第一的 Codex CLI (60.4%) 差距并不大，考虑到代码量的巨大差异，这是一个惊人的成绩。</li>
</ul>
<p>更有趣的是，测试中表现优异的 Terminus 2 也是一个极简 Agent——它只给模型一个 tmux 会话，没有任何其他工具。这强有力地证明了：对于强大的模型来说，最原始的接口（Terminal）往往是最有效的。</p>
<h2>小结：构建属于你的 Agentic Workflow</h2>
<p>Pi (OpenClaw的内置Agent) 的故事告诉我们：在 AI 时代，软件工程的护城河不在于你堆砌了多少功能，而在于你对模型能力的深刻理解和对架构的极度克制。</p>
<ul>
<li>透明胜过黑盒：让记忆和计划变成可见的 Markdown 文件。</li>
<li>通用胜过专用：Bash 是 Agent 与世界交互的通用语。</li>
<li>极简胜过繁杂：每一个多余的 Token 都是对模型智商的侮辱。</li>
</ul>
<p>如果你厌倦了现有工具的笨重与封闭，不妨参考 Pi 的思路，利用 pi-ai 这样的基础设施，去构建一个真正懂你、且完全受你掌控的 Coding Agent。</p>
<p>这不只是造轮子，这是在定义 AI 时代的“开发者尊严”。</p>
<p>资料链接：</p>
<ul>
<li>https://mariozechner.at/posts/2025-11-30-pi-coding-agent/</li>
<li>https://github.com/badlogic/pi-mono</li>
</ul>
<hr />
<p><strong>你认为 AI 工具该“重”还是“轻”？</strong></p>
<p>面对日益臃肿的 AI 插件，你是否也渴望回归那种“只有 4 个工具”的极简掌控感？在你的开发流中，有哪些功能是你觉得完全多余、甚至干扰了你的“心流”的？你认同“完全信任（YOLO）”这种安全哲学吗？</p>
<p>欢迎在评论区分享你的极客观点！</p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/02/15/openclaw-core-engine-pi-architecture-philosophy-minimalism/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从“手搓 Prompt”到“无限循环”：AI 编码的下一个形态是“Ralph”吗？</title>
		<link>https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph/</link>
		<comments>https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph/#comments</comments>
		<pubDate>Wed, 21 Jan 2026 00:00:41 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AgenticCoder]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[AIProgramming]]></category>
		<category><![CDATA[AI编程]]></category>
		<category><![CDATA[AutomationFeedbackLoop]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[ContextEngineering]]></category>
		<category><![CDATA[CursedLang]]></category>
		<category><![CDATA[Declarative]]></category>
		<category><![CDATA[DesiredState]]></category>
		<category><![CDATA[GeoffHuntley]]></category>
		<category><![CDATA[Imperative]]></category>
		<category><![CDATA[InfiniteLoop]]></category>
		<category><![CDATA[Overbaking]]></category>
		<category><![CDATA[Prompt]]></category>
		<category><![CDATA[PROMPT.md]]></category>
		<category><![CDATA[PromptEngineering]]></category>
		<category><![CDATA[RalphWiggumTechnique]]></category>
		<category><![CDATA[Ralph循环]]></category>
		<category><![CDATA[Refactoring]]></category>
		<category><![CDATA[SDD]]></category>
		<category><![CDATA[SelfCorrection]]></category>
		<category><![CDATA[SourcegraphAmp]]></category>
		<category><![CDATA[SpecDrivenDevelopment]]></category>
		<category><![CDATA[Specs]]></category>
		<category><![CDATA[上下文工程]]></category>
		<category><![CDATA[命令式]]></category>
		<category><![CDATA[声明式]]></category>
		<category><![CDATA[开发范式]]></category>
		<category><![CDATA[提示词工程]]></category>
		<category><![CDATA[无限循环]]></category>
		<category><![CDATA[系统架构师]]></category>
		<category><![CDATA[终局状态]]></category>
		<category><![CDATA[编码范式]]></category>
		<category><![CDATA[自动化反馈循环]]></category>
		<category><![CDATA[自我修复]]></category>
		<category><![CDATA[规格说明书]]></category>
		<category><![CDATA[规范驱动开发]]></category>
		<category><![CDATA[软件架构师]]></category>
		<category><![CDATA[过度烘焙]]></category>
		<category><![CDATA[重构]]></category>
		<category><![CDATA[验收测试员]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5754</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph 大家好，我是Tony Bai。 “如果你把 AI 放在一个死循环里，给它足够的权限和上下文，会发生什么？” 2025 年底，一个名为 “Ralph Wiggum Technique” (Ralph 循环) 的 AI 编程技巧在硅谷极客圈一夜爆红。它没有复杂的架构，没有花哨的界面，其核心代码甚至只有一行 Bash 脚本。 但就是这个看似简陋、甚至有些“诅咒”意味的技巧，却让开发者们在一夜之间重构了 6 个代码库，构建了全新的编程语言，甚至引发了 Anthropic 官方下场发布插件。 什么是 Ralph？为什么它如此有效？它又预示着怎样的 AI 编程未来？ Ralph 的诞生——一行代码的暴力美学 Ralph 的故事始于 Geoff Huntley 的一个疯狂实验。他没有使用复杂的 Agent 框架，而是写下了这样一行 Bash 脚本： while :; do cat PROMPT.md &#124; npx --yes @sourcegraph/amp ; done 这就是 Ralph 的全部。 PROMPT.md：这是唯一的输入，包含了项目的目标、规范、当前状态的描述（通常由 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/ai-coding-evolution-from-prompting-to-ralph-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph">本文永久链接</a> &#8211; https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph</p>
<p>大家好，我是Tony Bai。</p>
<p>“如果你把 AI 放在一个死循环里，给它足够的权限和上下文，会发生什么？”</p>
<p>2025 年底，一个名为 <strong>“<a href="https://ghuntley.com/ralph/">Ralph Wiggum Technique</a>” (Ralph 循环)</strong> 的 AI 编程技巧在硅谷极客圈一夜爆红。它没有复杂的架构，没有花哨的界面，其核心代码甚至只有一行 Bash 脚本。</p>
<p>但就是这个看似简陋、甚至有些“诅咒”意味的技巧，却让开发者们在一夜之间重构了 6 个代码库，构建了全新的编程语言，甚至引发了 Anthropic 官方下场发布插件。</p>
<p>什么是 Ralph？为什么它如此有效？它又预示着怎样的 AI 编程未来？</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/gemini-cli-starting-guide-qr.png" alt="" /></p>
<h2>Ralph 的诞生——一行代码的暴力美学</h2>
<p>Ralph 的故事始于 Geoff Huntley 的一个疯狂实验。他没有使用复杂的 Agent 框架，而是写下了这样一行 Bash 脚本：</p>
<pre><code class="bash">while :; do cat PROMPT.md | npx --yes @sourcegraph/amp ; done
</code></pre>
<p>这就是 Ralph 的全部。</p>
<ul>
<li><strong>PROMPT.md</strong>：这是唯一的输入，包含了项目的目标、规范、当前状态的描述（通常由 AI 自动更新）。</li>
<li><strong>@sourcegraph/amp</strong>：这是一个极其简单的 CLI 工具，它读取提示词，调用 LLM，并在当前目录下执行命令（修改文件、运行测试等）。</li>
<li><strong>while :; do &#8230; done</strong>：这就是灵魂所在。<strong>无限循环。</strong></li>
</ul>
<p>Ralph 不会停下来问你“这样行吗？”。它只是不断地读取目标、执行操作、再次读取目标、再次执行……直到你手动杀掉进程，或者它把代码库变成一团乱麻（所谓的“Overbaking”）。</p>
<h2>为什么 Ralph 有效？—— Context Engineering 的胜利</h2>
<p>乍一看，Ralph 似乎只是一个不可控的随机代码生成器。但实际上，它的成功揭示了 AI 编程的一个核心真理：<strong>上下文工程 (Context Engineering) 远比 Prompt 技巧更重要。</strong></p>
<p>Ralph 的核心不在于那个 Bash 循环，而在于那个 <strong>PROMPT.md</strong>（或者更高级的“Specs”）。</p>
<h3>声明式而非命令式</h3>
<p>传统的 AI 辅助编程是“命令式”的：你告诉 AI “修改这个函数”、“修复那个 Bug”。</p>
<p>Ralph 是“声明式”的：你在 PROMPT.md 中描述<strong>项目的终局状态</strong>（Desired State），比如“所有的 React 组件必须使用 TypeScript 且没有 default exports”。Ralph 的工作就是不断逼近这个状态。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/ai-coding-evolution-from-prompting-to-ralph-2.png" alt="" /></p>
<h3>小切口，高频迭代</h3>
<p>Ralph 并不试图一次性完成所有工作。它在每次循环中只处理一小块任务。这种“切碎”的工作方式，完美契合了 LLM 当前的上下文窗口限制，避免了“一次性生成几千行代码然后全错”的灾难。</p>
<h3>自动化反馈循环</h3>
<p>在 Ralph 的循环中，测试结果、Linter 报错、编译失败信息，都会成为下一个循环的输入。它不仅是在写代码，更是在<strong>自我修复</strong>。</p>
<h2>Ralph 的进化——从玩具到生产力</h2>
<p>随着社区的介入，Ralph 迅速从一个 Bash 玩具进化为一种严肃的开发范式。</p>
<ul>
<li><strong>重构利器</strong>：这是一次真实的重构经历。面对一个混乱的 React 前端，没有人工介入手动修改，而是花 30 分钟写了一份 REACT_CODING_STANDARDS.md（编码规范），然后让 Ralph 跑了 6 个小时。结果？Ralph 自主完成了一个人类可能需要数天才能完成的枯燥重构。</li>
<li><strong>Cursed Lang</strong>：Geoff 甚至用 Ralph 构建了一门全新的编程语言 Cursed Lang，包含编译器、标准库，且实现了自举。</li>
<li><strong>官方下场</strong>：Anthropic 甚至推出了官方的 Ralph 插件。虽然被社区吐槽“过度设计”且不如 Bash 脚本好用，但这标志着这种模式已被主流认可。</li>
</ul>
<h2>警惕“Overbaking”——AI 也会“把菜烧焦”</h2>
<p>Ralph 并非完美。它最大的风险在于 <strong>“Overbaking”（过度烘焙）</strong>。</p>
<p>如果你让 Ralph 跑得太久，且 PROMPT.md 的约束不够紧，它可能会开始产生“幻觉”般的优化：添加没人需要的 Post-Quantum 密码学支持、过度拆分文件、甚至为了通过测试而删除测试。</p>
<p>这给我们的启示是：<strong>AI 是强大的引擎，但人类必须是方向盘。</strong></p>
<ul>
<li><strong>写好 Spec</strong>：如果你的 Spec（规格说明书）是垃圾，Ralph 产出的代码也是垃圾。</li>
<li><strong>监控循环</strong>：不要让它无限制地跑下去，设置检查点。</li>
<li><strong>小步快跑</strong>：最好的 Ralph 实践是“一夜重构一个模块”，而不是“一夜重构整个系统”。</li>
</ul>
<h2>小结：Agentic Coder 的未来</h2>
<p>Ralph Wiggum Technique 可能只是 AI 编程进化史上的一朵浪花，但它留下的遗产是深远的。</p>
<p>它告诉我们，未来的编程可能不再是编写具体的逻辑，而是<strong>编写和维护一份完美的 Spec（规范说明书）</strong>。我们将成为“系统架构师”和“验收测试员”，而将那个枯燥、重复、且容易出错的“编码循环”，交给不知疲倦的 Ralph 们。</p>
<p>所以，下一次当你面对一座巨大的“屎山”代码时，不妨试着写一份清晰的 Spec，然后启动那个神奇的 Bash 循环。</p>
<p>资料链接：</p>
<ul>
<li>https://ghuntley.com/ralph/</li>
<li>https://www.humanlayer.dev/blog/brief-history-of-ralph</li>
</ul>
<hr />
<p><strong>从“暴力循环”到“优雅指挥”</strong></p>
<p>Ralph Wiggum 的故事让我们看到了 AI 自主编程的雏形：<strong>只要有正确的 Spec（规范）和自动化的 Loop（循环），奇迹就会发生。</strong></p>
<p>但 Ralph 毕竟只是一个 5 行代码的 Bash 脚本，粗糙且容易“烤糊”。在真实的工程实践中，我们不能只靠运气的“无限循环”，我们需要一套更稳定、更可控、更专业的<strong>AI 原生开发体系</strong>。</p>
<p>如果你不想止步于 Ralph 这样的极客实验，而是想真正掌握驾驭 AI Agent 的系统方法，欢迎加入我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong>。</p>
<p>这是关于如何构建你的“自动化流水线”：</p>
<ul>
<li><strong>告别低效</strong>：不再做“复制粘贴喂 AI”的搬运工，建立自动化闭环。</li>
<li><strong>驾驭神器</strong>：深度实战 <strong>Claude Code</strong> 等前沿工具，它是比 Ralph 更成熟的“神灯精灵”。</li>
<li><strong>身份跃迁</strong>：从被动的“AI 使用者”，进化为定义规范、掌控全局的<strong>“工作流指挥家”</strong>。</li>
</ul>
<p><strong>扫描下方二维码，别让 AI 只有暴力，让我们赋予它工程的优雅。</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/21/ai-coding-evolution-from-prompting-to-ralph/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>拆解 Claude Code：Coding Agent 终于“能用”背后的架构真相</title>
		<link>https://tonybai.com/2026/01/08/how-claude-code-works/</link>
		<comments>https://tonybai.com/2026/01/08/how-claude-code-works/#comments</comments>
		<pubDate>Thu, 08 Jan 2026 00:06:12 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[ActionoverText]]></category>
		<category><![CDATA[Anthropic]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[Claude.md]]></category>
		<category><![CDATA[Claude4.5Sonnet]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[Codex]]></category>
		<category><![CDATA[CodingAgent]]></category>
		<category><![CDATA[ConciseOutput]]></category>
		<category><![CDATA[ContextManagement]]></category>
		<category><![CDATA[Cursor]]></category>
		<category><![CDATA[DAG]]></category>
		<category><![CDATA[Glob]]></category>
		<category><![CDATA[grep]]></category>
		<category><![CDATA[Handoff]]></category>
		<category><![CDATA[Headless]]></category>
		<category><![CDATA[InferenceTimeCompute]]></category>
		<category><![CDATA[JaredZoneraich]]></category>
		<category><![CDATA[MasterWhileLoop]]></category>
		<category><![CDATA[PromptEngineering]]></category>
		<category><![CDATA[RAG]]></category>
		<category><![CDATA[SDD]]></category>
		<category><![CDATA[SelfCorrection]]></category>
		<category><![CDATA[Skills]]></category>
		<category><![CDATA[SourcegraphAmp]]></category>
		<category><![CDATA[Subagents]]></category>
		<category><![CDATA[SystemPrompt]]></category>
		<category><![CDATA[Tasks]]></category>
		<category><![CDATA[ThinkingKnobs]]></category>
		<category><![CDATA[ToDoList]]></category>
		<category><![CDATA[UnifiedDiff]]></category>
		<category><![CDATA[上下文管理]]></category>
		<category><![CDATA[子智能体]]></category>
		<category><![CDATA[实用主义]]></category>
		<category><![CDATA[循环架构]]></category>
		<category><![CDATA[推理时计算]]></category>
		<category><![CDATA[提示词工程]]></category>
		<category><![CDATA[有向无环图]]></category>
		<category><![CDATA[架构真相]]></category>
		<category><![CDATA[系统提示词]]></category>
		<category><![CDATA[自我修复]]></category>
		<category><![CDATA[规范驱动开发]]></category>
		<category><![CDATA[逆向工程]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5690</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2026/01/08/how-claude-code-works 大家好，我是Tony Bai。 在过去两年里，我们见证了 AI Coding Agent的尴尬童年：从最初笨拙的 Copy-Paste，到 Cursor 的 VS Code Fork 革命，再到如今 Claude Code 这种 CLI Coding Agent的出现。 为什么以前的 Agent 总是卡在“演示很酷，实战很废”的怪圈里？而 Claude Code 究竟做对了什么，让它突然变得如此顺手？ 答案可能出乎意料的枯燥：不是魔法，是更好的模型加上更“傻瓜”的架构。 这不是一篇 Anthropic 的官方通稿。本文基于 PromptLayer 创始人 Jared Zoneraich 的深度逆向工程与实战分享。我们扒开了 Claude Code 的外衣，试图还原 Coding Agent 从“玩具”进化为“神器”的技术跃迁路径。 架构哲学：删繁就简 如果你在 2024 年开发过 Agent，你一定画过那种复杂的 DAG（有向无环图）： “如果用户想退款，跳到节点 A；如果想查询，跳到节点 B……” 为了防止幻觉，我们设计了无数个分类器（Classifiers）和路由（Routers）。 结果呢？我们得到了一张维护噩梦般的蜘蛛网。 Claude [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2026/01/08/how-claude-code-works">本文永久链接</a> &#8211; https://tonybai.com/2026/01/08/how-claude-code-works</p>
<p>大家好，我是Tony Bai。</p>
<p>在过去两年里，我们见证了 AI Coding Agent的尴尬童年：从最初笨拙的 Copy-Paste，到 Cursor 的 VS Code Fork 革命，再到如今 Claude Code 这种 CLI Coding Agent的出现。</p>
<p>为什么以前的 Agent 总是卡在“演示很酷，实战很废”的怪圈里？而 Claude Code 究竟做对了什么，让它突然变得如此顺手？</p>
<p>答案可能出乎意料的枯燥：<strong>不是魔法，是更好的模型加上更“傻瓜”的架构。</strong></p>
<p>这不是一篇 Anthropic 的官方通稿。本文基于 PromptLayer 创始人 Jared Zoneraich 的深度逆向工程与<a href="https://www.youtube.com/watch?v=RFKCzGlAU6Q">实战分享</a>。我们扒开了 Claude Code 的外衣，试图还原 Coding Agent 从“玩具”进化为“神器”的技术跃迁路径。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/gemini-cli-starting-guide-qr.png" alt="img{512x368}" /></p>
<h2>架构哲学：删繁就简</h2>
<p>如果你在 2024 年开发过 Agent，你一定画过那种复杂的 <strong>DAG（有向无环图）</strong>：</p>
<ul>
<li>“如果用户想退款，跳到节点 A；如果想查询，跳到节点 B……”</li>
<li>为了防止幻觉，我们设计了无数个分类器（Classifiers）和路由（Routers）。</li>
</ul>
<p>结果呢？我们得到了一张维护噩梦般的蜘蛛网。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-9.png" alt="" /></p>
<p>Claude Code（以及 Gemini Cli、CodeX 等新一代Cli Coding Agent）的架构哲学可以用 Python 之禅概括：<strong>Simple is better than complex.</strong></p>
<p>它们抛弃了复杂的 DAG，拥抱了 <strong>Master While Loop</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-5.png" alt="" /></p>
<p>我们再用更为详细一些的伪代码来诠释这个master loop：</p>
<pre><code class="python"># Claude Code 的核心逻辑伪代码
messages = [...]
while True:
    response = model.generate(messages)
    if not response.tool_calls:
        break

    for tool in response.tool_calls:
        result = execute_tool(tool)
        messages.append(format_result(result))
</code></pre>
<p>就这么简单。<strong>Give it tools, and get out of the way.</strong></p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-2.png" alt="" /></p>
<p>这种架构的自信来源于模型能力的提升。现在的模型（如 Claude 4.5 Sonnet）已经足够聪明，能够自己决定“我需要先 grep 一下代码，发现不对，再 ls 一下目录，最后 edit 文件”。它不需要你预设路径，它需要的是<strong>自由探索的空间</strong>。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-3.png" alt="" /><br />
<center>来自https://arcprize.org/leaderboard(2026.1)</center></p>
<h2>工具箱揭秘：Bash 即正义 (The Tools)</h2>
<p>Claude Code 的工具箱极其精简，但每一个都切中要害。Jared 在逆向分析后发现，这套工具集本质上是在<strong>模拟一个人类高级工程师在终端里的行为</strong>。(注：按照Jared的说法，这些工具箱中的工具可能随Claude Code的版本的变化而不同!)</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-6.png" alt="" /></p>
<h3>Bash: The Universal Adapter</h3>
<p>如果只保留一个工具，那就是 <strong>Bash</strong>。</p>
<ul>
<li>它能跑脚本、能运行测试、能安装依赖、甚至能重启服务。</li>
<li>它是 Agent 与数字世界交互的通用接口。</li>
<li>最重要的是，LLM 训练数据里有海量的 Bash 语料，模型天生就是 Bash 高手。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-7.png" alt="" /></p>
<h3>Edit: Unified Diff</h3>
<p>Claude Code 没有选择全量重写文件（Rewrite），而是选择了 <strong>Diff</strong>。</p>
<ul>
<li><strong>省 Token</strong>：只输出修改的几行，上下文窗口压力骤减。</li>
<li><strong>速度快</strong>：更少的输出意味着更低的延迟。</li>
<li><strong>容错高</strong>：就像老师批改作文划红线一样，基于上下文的 Diff 修改比凭空重写整段代码更容易命中，也更容易被人类 Review。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-13.png" alt="" /></p>
<h3>Grep &amp; Glob > RAG</h3>
<p>还记得那些为了让 Agent 理解代码库而建立的复杂向量数据库（Vector DB）吗？Claude Code 说：<strong>不需要</strong>。</p>
<p>它直接使用 grep 和 glob。这不仅是因为现在的 Context Window 够大，更是因为这符合工程师的直觉。当你接手一个新项目时，你不会先在大脑里建立一个向量索引，你会先 ls 看看目录结构，然后 grep 关键字。<strong>模拟人类的行为，往往是最佳策略。</strong></p>
<h3>Sub-Agents (Tasks)</h3>
<p>当任务太复杂，上下文快爆了怎么办？Claude Code 引入了 <strong>Task</strong> 工具。</p>
<p>它可以启动一个子 Agent（Sub-agent），拥有独立的上下文，去执行特定的任务（比如“阅读完所有文档并总结 API 用法”），然后只将最终结果返回给主 Agent。这有效地解决了 Context 污染问题。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-12.png" alt="" /></p>
<h2>核心心法：相信模型，放弃微操</h2>
<p>在传统软件工程中，我们习惯于通过代码控制一切：if 条件 A 发生，执行 B。但在构建 Coding Agent 时，这种“控制欲”往往是最大的敌人。</p>
<p>Jared 分享了一个极具启发性的<strong>失败案例</strong>：</p>
<p>为了让 Agent 更好地操作 PromptLayer 的网页后台，他曾试图进行“人工辅助”——给网页上的每个按钮都加上了详细的 Title 和标签，试图告诉 Agent “点击这里会发生什么”。</p>
<p>结果呢？<strong>Agent 的表现反而变差了。</strong></p>
<p>为什么？因为额外的信息变成了噪音，分散了模型的注意力。模型原本可以通过“观察-尝试-纠错”的循环自己搞定任务，但人类的“硬编码微操”反而限制了模型的泛化能力。</p>
<h3>Exploration > Hardcoding</h3>
<p>Claude Code 的设计哲学是：<strong>当你有疑问时，相信模型(rely on the model)。</strong></p>
<ul>
<li><strong>不要预设所有边缘情况</strong>：以前我们会写一堆正则来解析输出，现在？直接把错误扔回给模型：“你报错了，修好它。”</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-10.png" alt="" /></p>
<ul>
<li><strong>探索即纠错</strong>：模型不仅能写代码，还能读懂报错信息。Claude Code 之所以强大，不是因为它一次就能写对，而是因为它在 Master Loop 中具备了<strong>自我修复（Self-Correction）</strong>的能力。</li>
</ul>
<p><strong>工程师的直觉是“把路铺好”，但 AI 时代的直觉应该是“给它地图，让它自己走”。</strong></p>
<h2>那些“不起眼”但天才的细节</h2>
<h3>Constitution: CLAUDE.md</h3>
<p>不需要复杂的微调，也不需要向量库。Claude Code 依靠项目根目录下的 CLAUDE.md 来理解项目规范。</p>
<p>这本质上是 <strong>Prompt Engineering 的胜利</strong>。它让配置变得透明、可读、可由用户（甚至 Agent 自己）随时修改。</p>
<h3>System Prompt 解密：像老板一样下指令</h3>
<p>Jared 分享了基于泄露信息的 Claude Code System Prompt 核心原则，这些原则非常值得我们借鉴：</p>
<ul>
<li><strong>Concise Output（极简输出）</strong>：除非用户要求细节，否则输出不要超过 4 行。</li>
<li><strong>No “Here is&#8230;”（拒绝废话）</strong>：不要说“好的，这是您的代码&#8230;”，直接给代码。Just do it.</li>
<li><strong>Action over Text（行动至上）</strong>：能用工具（Tool）解决的，别用文字解释。</li>
<li><strong>Style Match（风格一致）</strong>：严格匹配项目现有的代码风格。</li>
<li><strong>No Comments（拒绝注释）</strong>：除非用户要求，否则不要画蛇添足地加注释。</li>
<li><strong>Parallelism（并行执行）</strong>：鼓励并行运行命令，大规模搜索，并使用 TodoWrite 跟踪进度。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-14.png" alt="" /></p>
<p>这些指令的目的只有一个：<strong>让 Agent 看起来更像一个干练的 Senior Engineer，而不是一个啰嗦的 Chatbot。</strong></p>
<h3>Skills: 可扩展的 “System Prompt”</h3>
<p>随着任务变复杂，System Prompt 会越来越长，甚至超过 Context 限制。Claude Code 引入了 <strong>Skills</strong> 机制。</p>
<p>你可以把它理解为<strong>按需加载的“技能包”</strong>。Agent 会根据当前任务，决定是否加载额外的上下文或能力。</p>
<p><strong>典型应用场景：</strong></p>
<ul>
<li><strong>Documentation Updates</strong>：加载特定的文档写作风格指南。</li>
<li><strong>Design Style Guide</strong>：在写前端代码时，加载 UI 设计规范。</li>
<li><strong>Deep Research</strong>：加载深度搜索和总结的能力。</li>
<li><strong>DOCX/Excel Processing</strong>：甚至可以加载处理办公文档的技能（Jared 提到这是很多人没想到的用法）。</li>
</ul>
<h3>To-Do Lists: 提示词驱动的结构化</h3>
<p>当你让 Claude Code 干活时，它往往会先列一个 To-Do List(是不是又和人类干活的方式类似呢)。</p>
<p>有趣的是，这<strong>不是</strong>代码里写死的逻辑，而是 System Prompt 诱导出来的行为。</p>
<ul>
<li>它给用户一种“确定性”的心理安全感。</li>
<li>它支持<strong>断点续传</strong>：即使程序 Crash 了，重新把 To-Do List 喂给模型，它也能知道下一步该干嘛。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-8.png" alt="" /></p>
<h3>Thinking Knobs</h3>
<p>Think, Think Hard, Ultra Think。</p>
<p>这不仅仅是噱头，这是把 <strong>Inference-Time Compute（推理时计算）</strong> 变成了一个可调节的参数。对于复杂的重构，你可以让它“多想一会儿”；对于简单的 Bug fix，直接干就是了。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-11.png" alt="" /></p>
<h2>市场格局：没有全局最优解</h2>
<p>在 Coding Agent 的战场上，没有唯一的王者，只有不同的流派（The “AI Therapist” Problem）。</p>
<ul>
<li><strong>Claude Code</strong>：<strong>CLI 极简主义</strong>。简单、直观，适合不想离开终端的开发者。</li>
<li><strong>Cursor</strong>：<strong>UI 速度流</strong>，极致的响应速度。它利用用户数据飞轮，让体验越来越丝滑。</li>
<li><strong>OpenAI CodeX</strong>：<strong>内核硬核派(rust实现)</strong>。更关注底层的沙箱安全（Kernel-level Sandboxing），适合企业级、高安全要求的场景。</li>
<li><strong>Sourcegraph Amp</strong>：<strong>Web 协作流</strong>。主打 <strong>Handoff（接力）</strong> 机制，在一个 Agent 搞不定时，无缝切换到另一个 Context 或模型(无需用户选择)，像接力赛一样解决问题。</li>
</ul>
<h2>核心启示：Claude Code 教给我们的 5 条构建法则</h2>
<p>在演讲的最后，Jared 总结了 Claude Code 成功的 5 个核心要素。对于任何想要构建 Agent 或由 AI 驱动的应用的开发者来说，这 5 条法则就是当下的“金科玉律”。</p>
<h3>Trust in the model (相信模型)</h3>
<p>不要试图用传统的代码逻辑去“微操”模型。</p>
<ul>
<li><strong>反直觉</strong>：工程师总想把所有路都铺好（比如给网页按钮加详细标签）。</li>
<li><strong>新常识</strong>：模型的泛化能力和纠错能力远超你的硬编码规则。当遇到不确定性时，给它目标，让它自己去探索，而不是给它僵化的步骤。</li>
</ul>
<h3>Simple design wins (简单致胜)</h3>
<p>架构越简单越好。</p>
<ul>
<li><strong>拒绝复杂</strong>：不要搞几百个节点的 DAG（有向无环图），不要搞复杂的路由网络。</li>
<li><strong>拥抱简单</strong>：一个死循环（While Loop）加上强大的模型，往往能击败精心设计的复杂架构。正如 Python 之禅所说：“Simple is better than complex.”</li>
</ul>
<h3>Bash is all you need (Bash 足矣)</h3>
<p>在工具选择上，不要重新发明轮子。</p>
<ul>
<li><strong>通用接口</strong>：Bash 是在这个星球上运行代码最通用的接口，也是 LLM 训练数据中最丰富的语料之一。</li>
<li><strong>少即是多</strong>：与其开发 50 个专用的 Tool（比如 create_file, delete_file, git_commit&#8230;），不如只给它一个 bash 工具。模型知道怎么用 touch, rm, git。</li>
</ul>
<h3>Context management matters (上下文管理是关键)</h3>
<p>这是目前 Agent 最大的隐形杀手（The Boogeyman）。</p>
<ul>
<li><strong>瓶颈</strong>：无论模型多聪明，上下文窗口一旦被垃圾信息填满，智商就会直线下降。</li>
<li><strong>策略</strong>：必须把“上下文清洗”作为架构的一等公民。利用 Summarization（摘要）、Handoff（接力）或 Sub-agents（子智能体）机制，时刻保持主线程的清爽。</li>
</ul>
<h3>Different perspectives for different problems (不同问题，不同视角)</h3>
<p>没有“万能药”。Coding Agent 领域不存在全局最优解（Global Maxima）。</p>
<ul>
<li><strong>Claude Code</strong>：赢在 CLI 交互和复杂的 Git/环境管理，适合“不想离开终端”的场景。</li>
<li><strong>Cursor</strong>：赢在 UI 速度和代码补全，适合“快速编写”的场景。</li>
<li><strong>CodeX</strong>：赢在底层沙箱安全。</li>
<li><strong>结论</strong>：不要试图寻找一个能打败所有人的 Agent，而是要构建最适合特定场景（User Persona）的 Agent。</li>
</ul>
<p><img src="https://tonybai.com/wp-content/uploads/2026/how-claude-code-works-15.png" alt="" /></p>
<h2>小结</h2>
<p>Claude Code 的出现，标志着 Coding Agent 进入了<strong>“实用主义”</strong>时代。它不再是炫技的玩具，而是通过做减法（Less RAG, Less DAG, Less Guardrails），回归了软件工程的本质。</p>
<p>未来，我们或许不再直接调用 LLM 的 API，而是直接调用一个 Headless 的 run_agent() SDK，让它在后台默默地帮我们修 Bug、写文档、提 PR。</p>
<p><strong>最好的工具，就是当你感觉不到它存在的时候。</strong></p>
<p>资料来源：Jared Zoneraich “How Claude Code Works” &#8211; https://www.youtube.com/watch?v=RFKCzGlAU6Q</p>
<hr />
<p><strong>你的 Agent 构建心得</strong></p>
<p>Claude Code 的“极简架构”给我们上了一课。<strong>你在尝试构建 AI Agent 时，是否也曾陷入过“过度设计”的陷阱？对于“Bash is all you need”这个观点，你认同吗？</strong></p>
<p><strong>欢迎在评论区分享你的踩坑经历或架构思考！</strong> 让我们一起探索 Agent 开发的最佳路径。</p>
<p><strong>如果这篇文章为你揭开了 Claude Code 的神秘面纱，别忘了点个【赞】和【在看】，并转发给你的架构师朋友！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2026, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2026/01/08/how-claude-code-works/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bash 虽好，但我选 Go：如何用 10 倍代码换来 100 倍的维护性？</title>
		<link>https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability/</link>
		<comments>https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability/#comments</comments>
		<pubDate>Wed, 24 Dec 2025 04:00:45 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AI辅助编程]]></category>
		<category><![CDATA[AWSSSM]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[BoilerplateCode]]></category>
		<category><![CDATA[ClaudeCode]]></category>
		<category><![CDATA[Copilot]]></category>
		<category><![CDATA[CrossPlatform]]></category>
		<category><![CDATA[Cursor]]></category>
		<category><![CDATA[debugging]]></category>
		<category><![CDATA[EngineeringGovernance]]></category>
		<category><![CDATA[EnvMap]]></category>
		<category><![CDATA[ExplicitContract]]></category>
		<category><![CDATA[GlueCode]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[Interface]]></category>
		<category><![CDATA[LLM]]></category>
		<category><![CDATA[Maintainability]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[StaticBinary]]></category>
		<category><![CDATA[StaticTypeChecking]]></category>
		<category><![CDATA[struct]]></category>
		<category><![CDATA[Testability]]></category>
		<category><![CDATA[ToolchainHell]]></category>
		<category><![CDATA[TypeSafety]]></category>
		<category><![CDATA[vault]]></category>
		<category><![CDATA[Verbosity]]></category>
		<category><![CDATA[可维护性]]></category>
		<category><![CDATA[接口抽象]]></category>
		<category><![CDATA[测试覆盖]]></category>
		<category><![CDATA[类型安全]]></category>
		<category><![CDATA[胶水代码]]></category>
		<category><![CDATA[脚本治理]]></category>
		<category><![CDATA[跨平台]]></category>
		<category><![CDATA[静态编译]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=5591</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability 大家好，我是Tony Bai。 “Bash 是一种很棒的胶水语言，但 Go 是更好的胶水。” 在日常开发中，我们经常会写一些 Bash 脚本来处理本地环境配置、启动 Docker 容器、同步密钥等琐碎任务。起初，它们只是几行简单的命令；但随着时间推移，它们逐渐膨胀成包含数百行 jq、sed、awk 的怪物，充斥着针对 macOS 和 Linux 的条件分支，以及“千万别动这行代码”的注释。 近日，一位开发者分享了他用 Go 重写这些 Bash 脚本的经历，引发了一场Go社区的关于工程可维护性与“胶水代码”治理的深度探讨。 在本文中，我们将跟随这位开发者的视角，深入剖析这次从脚本到工程的“降熵”之旅，并探讨在 AI 辅助编程日益普及的今天，这一选择背后的新逻辑。 Bash 脚本的“熵增”之路 许多团队的本地开发环境脚本，往往始于一个简单的需求：从 AWS SSM 或 Vault 拉取密钥，生成 .env 文件，然后启动服务。 最初的 Bash 脚本可能只有 10 行。但随着需求增加，它变成了这样： 工具链依赖地狱：脚本依赖特定版本的 sed、grep 或 jq。一旦某个同事更新了系统工具，脚本就挂了。 跨平台噩梦：sed 在 macOS 和 Linux 上的行为不一致，导致脚本中充斥着 if [[ [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/bash-vs-go-10x-code-100x-maintainability-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability">本文永久链接</a> &#8211; https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability</p>
<p>大家好，我是Tony Bai。</p>
<blockquote>
<p>“Bash 是一种很棒的胶水语言，但 Go 是更好的胶水。”</p>
</blockquote>
<p>在日常开发中，我们经常会写一些 Bash 脚本来处理本地环境配置、启动 Docker 容器、同步密钥等琐碎任务。起初，它们只是几行简单的命令；但随着时间推移，它们逐渐膨胀成包含数百行 jq、sed、awk 的怪物，充斥着针对 macOS 和 Linux 的条件分支，以及“千万别动这行代码”的注释。</p>
<p>近日，一位开发者<a href="https://www.reddit.com/r/golang/comments/1pb7t1q/show_tell_bash_is_great_glue_go_is_better_glue/">分享了他用 Go 重写这些 Bash 脚本的经历</a>，引发了一场Go社区的关于<strong>工程可维护性</strong>与<strong>“胶水代码”治理</strong>的深度探讨。</p>
<p>在本文中，我们将跟随这位开发者的视角，深入剖析这次从脚本到工程的“降熵”之旅，并探讨在 AI 辅助编程日益普及的今天，这一选择背后的新逻辑。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/paid/system-programming-in-go-pr.png" alt="" /></p>
<h2>Bash 脚本的“熵增”之路</h2>
<p>许多团队的本地开发环境脚本，往往始于一个简单的需求：从 AWS SSM 或 Vault 拉取密钥，生成 .env 文件，然后启动服务。</p>
<p>最初的 Bash 脚本可能只有 10 行。但随着需求增加，它变成了这样：</p>
<ul>
<li><strong>工具链依赖地狱</strong>：脚本依赖特定版本的 sed、grep 或 jq。一旦某个同事更新了系统工具，脚本就挂了。</li>
<li><strong>跨平台噩梦</strong>：sed 在 macOS 和 Linux 上的行为不一致，导致脚本中充斥着 if [[ "$OS" == "darwin" ]] 这样的分支。</li>
<li><strong>调试困难</strong>：当脚本出错时，你很难知道是哪一行管道（pipe）出了问题，也没有类型检查来帮你发现潜在错误。</li>
</ul>
<p>正如评论区一位开发者所言：“Bash 脚本就像是一堆没有明确所有权的‘杂物’。每个人都在上面打补丁，直到它变成一个没人敢碰的定时炸弹。”</p>
<h2>Go 作为“强力胶水”的优势</h2>
<p>原作者将这堆复杂的 Bash 逻辑重构为一个名为 envmap 的小型 Go CLI 工具。虽然代码行数可能增加了（Go 确实比 Bash 繁琐），但他收获了<strong>工程质量的质变</strong>：</p>
<h3>结构化配置与类型安全</h3>
<p>不再有脆弱的字符串解析。配置被定义为强类型的 struct，编译器会帮你检查拼写错误和类型不匹配。</p>
<pre><code class="go">// Bash: 祈祷这个字符串解析是对的...
// Go: 编译器保证它是对的
type Config struct {
    Env      string json:"env"
    Region   string json:"region"
    UseVault bool   json:"use_vault"
}
</code></pre>
<h3>接口抽象与可测试性</h3>
<p>原作者定义了一个 Provider 接口来抽象不同的密钥后端（AWS SSM, Vault, 本地文件）。这不仅让代码结构清晰，更重要的是，<strong>它变得可测试了</strong>。你可以轻松编写单元测试来验证逻辑，而无需真的连接到 AWS。</p>
<pre><code class="go">type Provider interface {
    Get(ctx context.Context, key string) (string, error)
    // ...
}
</code></pre>
<h3>跨平台的一致性</h3>
<p>Go 编译出的静态二进制文件，消除了“它在我的机器上能跑”的问题。无论同事使用 macOS、Linux 还是 Windows，他们运行的都是相同的逻辑，不再受系统自带 Shell 工具版本的影响。</p>
<h2>社区的思辨——“杀鸡用牛刀”吗？</h2>
<p>这场重构也引发了激烈的讨论。有开发者质疑：用 Go 写脚本是不是太重了？Python 或 TypeScript 岂不是更好的替代品？甚至，为什么不直接用 Makefile？</p>
<h3>反方观点：复杂度的转移</h3>
<ul>
<li><strong>“代码更多了”</strong>：Go 的 verbose（繁琐）是公认的。简单的 cp a b 在 Go 中需要写不少代码。</li>
<li><strong>“编译步骤”</strong>：虽然 go run很快，但毕竟多了一个编译环节。</li>
</ul>
<h3>正方观点：维护性的胜利</h3>
<ul>
<li><strong>“长期收益”</strong>：一位开发者分享了他将 40k 行 Bash/Perl 脚本重构为 10k 行 Go 代码的经历。虽然初期投入大，但获得了<strong>测试覆盖</strong>、<strong>文档化</strong>和<strong>零依赖部署</strong>的巨大收益。</li>
<li><strong>“显式契约”</strong>：Bash 脚本之间往往通过不稳定的文本流（stdout/stdin）通信，极其脆弱。而 Go 代码之间通过明确的接口和模块调用通信，更加稳健。</li>
</ul>
<p>正如一位评论者总结的：“如果你只是写一个 10 行的脚本，Bash 是完美的。但如果你的脚本开始需要处理复杂的逻辑、状态和错误，那么它就不再是一个脚本，而是一个<strong>程序</strong>。既然是程序，就应该用编写程序的语言（如 Go）来写。”</p>
<h2>AI 时代的变量——“繁琐”不再是借口</h2>
<p>在过去，阻碍开发者用 Go 替代 Bash 的最大阻力往往是<strong>编写效率</strong>。写一个几十行的 Go 程序来替换一行 sed 命令，听起来确实不仅“繁琐”，而且“低效”。</p>
<p>然而，在 AI 辅助编程（如 Copilot, Cursor, Claude Code等）普及的今天，这个天平正在发生倾斜。</p>
<h3>AI 为 Go 支付了“样板税”</h3>
<p>Go 语言的 verbose（繁琐）特性——显式的错误处理、结构体定义、库的引入——曾经是手写代码的负担。但在 AI 时代，这些标准化的样板代码恰恰是 <strong>LLM（大语言模型）最擅长生成的</strong>。</p>
<p>你只需要告诉 AI：“写一个 CLI，读取环境变量，请求 AWS SSM，如果有错误就打印红色日志。” AI 能瞬间生成 80% 的 Go 代码骨架。开发者只需专注于核心逻辑的微调。</p>
<h3>编译器是 AI 最好的“质检员”</h3>
<p>用 AI 生成 Bash 脚本是一场赌博。LLM 可能会编造出不存在的 awk 参数，或者写出在某些 Shell 下不兼容的语法，而这些错误往往要在运行时才能发现（甚至引发灾难性的 rm -rf）。</p>
<p>相比之下，用 AI 生成 Go 代码具有天然的<strong>安全屏障</strong>：</p>
<ul>
<li><strong>静态类型检查</strong>：如果 AI 幻觉了不存在的方法，编译器会立刻报错，而不是等到运行时崩溃。</li>
<li><strong>确定性</strong>：Go 的语法规范极其严格，减少了 AI 生成“虽然能跑但很奇怪”的代码的概率。</li>
</ul>
<p>正如原作者在回复中所承认的：“我使用了 Cursor 和 Codex，代码的复杂性主要来自业务逻辑，而非语言本身。” <strong>在 AI 的加持下，获得一个类型安全、跨平台、易维护的 Go 二进制文件，其生产效率已经并不输给编写和调试一个脆弱的 Bash 脚本。</strong></p>
<h2>小结：从脚本到工程，从手写到 AI 共生</h2>
<p>这个案例告诉我们，<strong>“胶水代码”也需要工程化治理</strong>。</p>
<p>当你的 Bash 脚本开始变得让你感到恐惧、难以维护时，不要犹豫，用 Go 重写它吧。虽然你会多写一些 if err != nil，但你换来的是<strong>确定性</strong>、<strong>可维护性</strong>和<strong>内心的宁静</strong>。</p>
<p>特别是在 AI 时代，Go 语言的“繁琐”已被智能助手和编码智能体消解，而它带来的“稳健”却愈发珍贵。Go 也许不是最简洁的胶水，但在 AI 的帮助下，它绝对是性价比最高、<strong>最牢固</strong>的胶水。</p>
<p>资料链接：https://www.reddit.com/r/golang/comments/1pb7t1q/show_tell_bash_is_great_glue_go_is_better_glue/</p>
<hr />
<p><strong>你的“胶水”选型</strong></p>
<p>“Bash 还是 Go/Python？”这可能是每个团队都会面临的选择题。<strong>在你的工作中，你会为多大规模的脚本选择改用 Go 或 Python 重写？你是否有过被复杂 Bash 脚本“坑”惨的经历？</strong></p>
<p><strong>欢迎在评论区分享你的“血泪史”或“重构心得”！</strong> 让我们一起探讨如何让工具代码更优雅。</p>
<p><strong>如果这篇文章给了你重构旧脚本的勇气，别忘了点个【赞】和【在看】，并分享给你的团队！</strong></p>
<hr />
<p>还在为“复制粘贴喂AI”而烦恼？我的新专栏 <strong>《<a href="http://gk.link/a/12EPd">AI原生开发工作流实战</a>》</strong> 将带你：</p>
<ul>
<li>告别低效，重塑开发范式</li>
<li>驾驭AI Agent(Claude Code)，实现工作流自动化</li>
<li>从“AI使用者”进化为规范驱动开发的“工作流指挥家”</li>
</ul>
<p>扫描下方二维码，开启你的AI原生开发之旅。</p>
<p><img src="https://tonybai.com/wp-content/uploads/2025/ai-native-dev-workflow-qr.png" alt="" /></p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/12/24/bash-vs-go-10x-code-100x-maintainability/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>代码之外的必修课：顶级技术文档风格指南如何提升你的工程效率</title>
		<link>https://tonybai.com/2025/07/14/writing-style-guide/</link>
		<comments>https://tonybai.com/2025/07/14/writing-style-guide/#comments</comments>
		<pubDate>Mon, 14 Jul 2025 12:30:40 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[eslint]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[gofmt]]></category>
		<category><![CDATA[PR]]></category>
		<category><![CDATA[README.md]]></category>
		<category><![CDATA[Redhat]]></category>
		<category><![CDATA[UI]]></category>
		<category><![CDATA[写作]]></category>
		<category><![CDATA[开源]]></category>
		<category><![CDATA[技术债]]></category>
		<category><![CDATA[文档]]></category>
		<category><![CDATA[沟通]]></category>
		<category><![CDATA[社区]]></category>
		<category><![CDATA[软技能]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4904</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2025/07/14/writing-style-guide 大家好，我是Tony Bai。 作为一名开发者、架构师或运维专家，我们大部分时间都在与代码、系统和架构打交道。然而，我们同样在持续不断地进行另一种形式的“编码”——沟通编码。无论是撰写一个清晰的 README.md，提交一份详尽的 Pull Request 描述，编写项目内部的技术文档，还是在社区中回答一个问题，我们都在扮演着“技术作者”的角色。 代码的质量决定了软件能否运行，而沟通的质量则决定了项目能否高效协作、知识能否有效传承、社区能否健康发展。一份糟糕的文档，如同晦涩难懂的“面条代码”，会极大地消耗团队的精力和热情。 最近，Redhat公司发布了《Red Hat Technical Writing Style Guide》7.1版本。这份指南不仅仅是一系列规则的集合，它更像是一部由顶级开源软件公司沉淀下来的、关于如何通过清晰沟通来提升工程效率的哲学。 在这篇文章中，我将提炼其中的一些精髓，探讨那些能直接提升您和团队工程能力的写作原则，供大家参考。 写作的“第一性原理”：清晰、精确、用户至上 技术文档的首要目标是传递信息，任何模糊、冗长或模棱两可的表达都是工程效率的天敌。指南强调了几个核心原则： 1. 拥抱主动语态，指令明确无误 主动语态让指令更直接、更有力。在指导性文档中，这能显著降低读者的认知负荷。 不推荐 (被动语态) 推荐 (主动语态) Linuxconf can be started by typing &#8230; Type &#8230; to start Linuxconf. 新的配置可以被应用通过重启服务。 重启服务以应用新的配置。 对开发者的价值：当用户（或未来的你）阅读操作手册时，清晰的指令意味着更低的出错率和更快的解决问题速度。 2. 杜绝冗余，尊重读者的时间 避免使用不必要的填充词，让每一句话都言之有物。 冗余 精炼 Perform the installation of the product. Install the [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/2025/writing-style-guide-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2025/07/14/writing-style-guide">本文永久链接</a> &#8211; https://tonybai.com/2025/07/14/writing-style-guide</p>
<p>大家好，我是Tony Bai。</p>
<p>作为一名开发者、架构师或运维专家，我们大部分时间都在与代码、系统和架构打交道。然而，我们同样在持续不断地进行另一种形式的“编码”——<strong>沟通编码</strong>。无论是撰写一个清晰的 README.md，提交一份详尽的 Pull Request 描述，编写项目内部的技术文档，还是在社区中回答一个问题，我们都在扮演着“技术作者”的角色。</p>
<p>代码的质量决定了软件能否运行，而<strong>沟通的质量则决定了项目能否高效协作、知识能否有效传承、社区能否健康发展</strong>。一份糟糕的文档，如同晦涩难懂的“面条代码”，会极大地消耗团队的精力和热情。</p>
<p>最近，Redhat公司发布了《<a href="https://www.stylepedia.net/style/">Red Hat Technical Writing Style Guide</a>》7.1版本。这份指南不仅仅是一系列规则的集合，它更像是一部由顶级开源软件公司沉淀下来的、关于<strong>如何通过清晰沟通来提升工程效率</strong>的哲学。</p>
<p>在这篇文章中，我将提炼其中的一些精髓，探讨那些能直接提升您和团队工程能力的写作原则，供大家参考。</p>
<h2>写作的“第一性原理”：清晰、精确、用户至上</h2>
<p>技术文档的首要目标是传递信息，任何模糊、冗长或模棱两可的表达都是工程效率的天敌。指南强调了几个核心原则：</p>
<h3>1. 拥抱主动语态，指令明确无误</h3>
<p>主动语态让指令更直接、更有力。在指导性文档中，这能显著降低读者的认知负荷。</p>
<table>
<thead>
<tr>
<th align="left">不推荐 (被动语态)</th>
<th align="left"><strong>推荐 (主动语态)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Linuxconf can be started by typing &#8230;</td>
<td align="left">Type &#8230; to start Linuxconf.</td>
</tr>
<tr>
<td align="left">新的配置<strong>可以被应用</strong>通过重启服务。</td>
<td align="left"><strong>重启服务</strong>以应用新的配置。</td>
</tr>
</tbody>
</table>
<p><strong>对开发者的价值</strong>：当用户（或未来的你）阅读操作手册时，清晰的指令意味着更低的出错率和更快的解决问题速度。</p>
<h3>2. 杜绝冗余，尊重读者的时间</h3>
<p>避免使用不必要的填充词，让每一句话都言之有物。</p>
<table>
<thead>
<tr>
<th align="left">冗余</th>
<th align="left"><strong>精炼</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Perform the installation of the product.</td>
<td align="left">Install the product.</td>
</tr>
<tr>
<td align="left">This problem is located on the /dev/sda1 partition.</td>
<td align="left">This problem is on the /dev/sda1 partition.</td>
</tr>
</tbody>
</table>
<h3>3. 避免歧义：This 指的是什么？</h3>
<p>在技术文档中，代词（如 this, that, it）是歧义的重灾区，尤其对于翻译和非母语阅读者。指南建议明确指出代词所指代的的名词。</p>
<pre><code>- A site can use these to self-assign a private routable IP address space.
+ A site can use these unique local addresses to self-assign a private routable IP address space.

- This causes SSH to lose the recorded identities.
+ This action causes SSH to lose the recorded identities.
</code></pre>
<p><strong>对开发者的价值</strong>：在复杂的配置说明或问题排查指南中，消除代词歧义可以防止因误解而导致的配置错误。</p>
<h2>为全球化社区而写：包容性与可翻译性</h2>
<p>开源项目和现代技术团队本质上是全球化的。我们的文档需要被不同文化背景的人阅读和翻译。</p>
<h3>1. 使用包容性语言</h3>
<p>这是现代技术社区的基石。避免使用可能带有偏见或冒犯性的术语，有助于建立一个更健康、更多元化的社区环境。</p>
<ul>
<li><strong>master/slave</strong> -> 推荐使用 primary/replica, controller/worker, leader/follower 等。</li>
<li><strong>whitelist/blacklist</strong> -> 推荐使用 allowlist/denylist 或 blocklist。</li>
<li><strong>性别代词</strong> -> 避免使用 he/she，推荐使用中性的 they（可指代单数）或直接使用第二人称 you。</li>
</ul>
<h3>2. 为翻译而设计</h3>
<p>糟糕的措辞会给机器翻译和人工翻译带来灾难。一些简单的规则可以极大地提升文档的可翻译性：</p>
<ul>
<li><strong>避免使用俚语和行话</strong>：eat your own dogfood (使用自己的产品), boil the ocean (范围过大) 等表达在其他文化中可能完全无法理解。</li>
<li><strong>慎用 may 和 should</strong>：may 可能表示“可能性”或“许可”，should 可能表示“建议”或“期望”。使用 can (可以), might (可能), must (必须) 会更精确。</li>
<li><strong>避免名词堆叠</strong>：Standard system log management configuration 这种连续名词的组合，在翻译时极易出错。可以调整为 Standard configuration of system log management。</li>
</ul>
<h2>工程师的文字“代码规范”：一致性与标准化</h2>
<p>如同 eslint 或 gofmt 为代码提供规范一样，风格指南为我们的文字提供了“格式化”标准。这能确保整个项目文档风格统一，易于阅读和维护。</p>
<h3>1. 统一命令语法文档</h3>
<p>在展示命令行示例时，保持一致的格式至关重要。</p>
<pre><code class="bash"># 一个清晰的命令语法示例
$ git clone [username@]hostname:/repository_filename [directory]
</code></pre>
<pre><code>- 使用 $ 表示普通用户，# 表示 root 用户。
- 使用 [] 表示可选参数。
- 使用斜体或描述性词语（如 _filename_）表示 需替换的值。
- 在需要省略输出时，使用 ...output omitted... 标记，而不是随意删减。
</code></pre>
<h3>2. 精确描述 UI 元素</h3>
<p>当描述用户界面时，精确和简洁是关键。</p>
<ul>
<li><strong>直接了当</strong>：不说 Click the Save button，而说 Click <strong>Save</strong>。</li>
<li><strong>名称匹配</strong>：文档中的 UI 元素名称（如按钮、菜单项）应与界面上显示的<strong>完全一致</strong>（包括大小写）。</li>
<li><strong>导航路径</strong>：使用 -> 或 →清晰地表示导航路径，例如：Go to Monitoring → Metrics。</li>
</ul>
<h3>3. 避免产品名称的所有格</h3>
<p>一个看似微小但能提升专业度的细节：</p>
<ul>
<li><strong>不推荐</strong>: Red Hat OpenShift&#8217;s Logging operator creates&#8230;</li>
<li><strong>推荐</strong>: The Red Hat OpenShift Logging operator creates&#8230;</li>
</ul>
<h2>总结与展望：将沟通视为工程技艺</h2>
<p>《红帽风格指南》带给我们的最大启示是：<strong>清晰、精确、专业的书面沟通不是一种“软技能”，而是工程技艺（Craftsmanship）不可或缺的一部分</strong>。它与编写高质量代码、设计健壮架构同等重要。</p>
<p>下一次，当你准备提交一个 Pull Request、更新一份 README，或撰写一篇技术博客时，不妨尝试运用其中的一两个原则：</p>
<ul>
<li>将一个被动语态的句子改为主动语态。</li>
<li>检查是否有模糊的代词 it 或 this 可以被替换。</li>
<li>思考一下你使用的术语是否足够包容和全球通用。</li>
</ul>
<p>投资于沟通，就是投资于整个团队的效率和项目的未来。正如一份优雅的代码令人赏心悦悦目，一份清晰的文档同样能带来极致的工程之美。</p>
<hr />
<p>你的Go技能，是否也卡在了“熟练”到“精通”的瓶颈期？</p>
<ul>
<li>想写出更地道、更健壮的Go代码，却总在细节上踩坑？</li>
<li>渴望提升软件设计能力，驾驭复杂Go项目却缺乏章法？</li>
<li>想打造生产级的Go服务，却在工程化实践中屡屡受挫？</li>
</ul>
<p>继《<a href="http://gk.link/a/10AVZ">Go语言第一课</a>》后，我的《<a href="http://gk.link/a/12yGY">Go语言进阶课</a>》终于在极客时间与大家见面了！</p>
<p>我的全新极客时间专栏 《<a href="http://gk.link/a/12yGY">Tony Bai·Go语言进阶课</a>》就是为这样的你量身打造！30+讲硬核内容，带你夯实语法认知，提升设计思维，锻造工程实践能力，更有实战项目串讲。</p>
<p>目标只有一个：助你完成从“Go熟练工”到“Go专家”的蜕变！ 现在就加入，让你的Go技能再上一个新台阶！</p>
<p><img src="https://tonybai.com/wp-content/uploads/course-card/iamtonybai-banner-2.gif" alt="" /></p>
<hr />
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求，请扫描下方公众号二维码，与我私信联系。</p>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p style='text-align:left'>&copy; 2025, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2025/07/14/writing-style-guide/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从DevOps到日常脚本：聊聊Go语言的多面性</title>
		<link>https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts/</link>
		<comments>https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts/#comments</comments>
		<pubDate>Mon, 07 Oct 2024 22:46:09 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[binfmt_misc]]></category>
		<category><![CDATA[Channel]]></category>
		<category><![CDATA[chmod]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[gobuild]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[gomacro]]></category>
		<category><![CDATA[gomodule]]></category>
		<category><![CDATA[gore]]></category>
		<category><![CDATA[goroutine]]></category>
		<category><![CDATA[gorun]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[PlatformEngineering]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[reddit]]></category>
		<category><![CDATA[REPL]]></category>
		<category><![CDATA[Rust]]></category>
		<category><![CDATA[SheBang]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[TIOBE]]></category>
		<category><![CDATA[yaegi]]></category>
		<category><![CDATA[平台工程]]></category>
		<category><![CDATA[并发]]></category>
		<category><![CDATA[标准库]]></category>
		<category><![CDATA[编译]]></category>
		<category><![CDATA[脚本]]></category>
		<category><![CDATA[跨平台]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=4322</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts 2024年初，TIOBE编程语言排行榜上，Go再次进入了前十，并在之后又成功冲高至第七名。 Go语言的排名上升，至少在Reddit Go论坛上帖子数量和在线人数上得到了体现，尽管目前与Rust热度仍有差距，但可见Go的关注度在提升： 2024年国庆节假期某天下午的实时在线数对比 随着Go语言人气的上升，论坛中的问题也变得愈发多样化。许多Gopher常常问及为何Go是DevOps语言和Go适合用作脚本语言吗等问题，这些都反映了Go语言的多面性。 从最初的系统编程语言，到如今在DevOps领域的广泛应用，再到一些场合被探索用作脚本语言，Go展现出了令人惊叹的灵活性和适应性。在本篇文章中，我们将聚焦于Go语言在DevOps领域的应用以及它作为脚本替代语言的潜力，聊聊其强大多面性如何满足这些特定场景的需求。 1. Go在DevOps中的优势 随着DevOps的发展，平台工程(Platform Engineering)这一新兴概念逐渐兴起。在自动化任务、微服务部署和系统管理中，编程语言的作用变得愈发重要。Go语言凭借其高性能、并发处理能力以及能够编译成单一二进制文件的特点，越来越受到DevOps领域开发人员的青睐，成为开发DevOps工具链的重要组成部分。 首先，Go的跨平台编译能力使得DevOps团队可以在一个平台上编译，然后在多个不同的操作系统和架构上运行，结合编译出的单一可执行文件的能力，大大简化了部署流程，这也是很多Go开发者认为Go适合DevOps的第一优势： $GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 main.go $GOOS=linux GOARCH=arm64 go build -o myapp-linux-arm64 main.go $GOOS=darwin GOARCH=amd64 go build -o myapp-darwin-amd64 main.go $GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe main.go 其次，Go的标准库仿佛“瑞士军刀”，开箱即用，为DevOps场景提供了所需的丰富的网络、加密和系统操作功能库，大幅降低对外部的依赖，即便不使用第三方包生态系统，也可以满足大部分的DevOps功能需求。 此外，Go的goroutines和channels为处理高并发任务提供了极大便利，这在DevOps中也尤为重要。例如，以下代码展示了如何使用goroutines并发检查多个服务的健康状态： func checkServices(services []string) { var wg sync.WaitGroup for _, service := [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/go-languages-versatility-from-devops-to-daily-scripts-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts">本文永久链接</a> &#8211; https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts</p>
<p>2024年初，TIOBE编程语言排行榜上，<a href="https://mp.weixin.qq.com/s?__biz=MzIyNzM0MDk0Mg==&amp;mid=2247497403&amp;idx=1&amp;sn=03bc972e38163e1539da765249d46586&amp;chksm=e860115adf17984cfe47f9680d8c0fb6370987ad45415ff2d38233d05fe6b315210ce6ada385#rd">Go再次进入了前十，并在之后又成功冲高至第七名</a>。</p>
<p>Go语言的排名上升，至少在<a href="https://www.reddit.com/r/golang/">Reddit Go论坛</a>上帖子数量和在线人数上得到了体现，尽管目前与<a href="https://tonybai.com/tag/rust">Rust</a>热度仍有差距，但可见Go的关注度在提升：</p>
<p><img src="https://tonybai.com/wp-content/uploads/go-languages-versatility-from-devops-to-daily-scripts-2.png" alt="" /><br />
<center>2024年国庆节假期某天下午的实时在线数对比</center></p>
<p>随着Go语言人气的上升，论坛中的问题也变得愈发多样化。许多Gopher常常问及<a href="https://www.reddit.com/r/golang/comments/1fqwbv0/why_is_golang_the_language_of_devops/">为何Go是DevOps语言</a>和<a href="https://www.reddit.com/r/golang/comments/1ftpk2m/do_you_use_go_for_scripts/">Go适合用作脚本语言吗</a>等问题，这些都反映了Go语言的多面性。</p>
<p>从最初的系统编程语言，到如今在DevOps领域的广泛应用，再到一些场合被探索用作脚本语言，Go展现出了令人惊叹的灵活性和适应性。在本篇文章中，我们将聚焦于Go语言在DevOps领域的应用以及它作为脚本替代语言的潜力，聊聊其强大多面性如何满足这些特定场景的需求。</p>
<h2>1. Go在DevOps中的优势</h2>
<p>随着DevOps的发展，<a href="https://en.wikipedia.org/wiki/Platform_engineering">平台工程(Platform Engineering)</a>这一新兴概念逐渐兴起。在自动化任务、微服务部署和系统管理中，编程语言的作用变得愈发重要。Go语言凭借其高性能、并发处理能力以及能够编译成单一二进制文件的特点，越来越受到DevOps领域开发人员的青睐，成为开发DevOps工具链的重要组成部分。</p>
<p>首先，Go的跨平台编译能力使得DevOps团队可以在一个平台上编译，然后在多个不同的操作系统和架构上运行，结合<strong>编译出的单一可执行文件</strong>的能力，大大简化了部署流程，这也是很多Go开发者认为Go适合DevOps的第一优势：</p>
<pre><code>$GOOS=linux GOARCH=amd64 go build -o myapp-linux-amd64 main.go
$GOOS=linux GOARCH=arm64 go build -o myapp-linux-arm64 main.go
$GOOS=darwin GOARCH=amd64 go build -o myapp-darwin-amd64 main.go
$GOOS=windows GOARCH=amd64 go build -o myapp-windows-amd64.exe main.go
</code></pre>
<p>其次，Go的标准库仿佛“瑞士军刀”，开箱即用，<strong>为DevOps场景提供了所需的丰富的网络、加密和系统操作功能库</strong>，大幅降低对外部的依赖，即便不使用第三方包生态系统，也可以满足大部分的DevOps功能需求。</p>
<p>此外，Go的goroutines和channels为处理高并发任务提供了极大便利，这在DevOps中也尤为重要。例如，以下代码展示了如何使用goroutines并发检查多个服务的健康状态：</p>
<pre><code>func checkServices(services []string) {
    var wg sync.WaitGroup
    for _, service := range services {
        wg.Add(1)
        go func(s string) {
            defer wg.Done()
            if err := checkHealth(s); err != nil {
                log.Printf("Service %s is unhealthy: %v", s, err)
            } else {
                log.Printf("Service %s is healthy", s)
            }
        }(service)
    }
    wg.Wait()
}
</code></pre>
<p>并且，许多知名的DevOps基础设施、中间件和工具都是用Go编写的，如Docker、Kubernetes、Prometheus等，集成起来非常丝滑。这些工具的成功进一步证明了Go在DevOps领域的适用性。</p>
<h2>2. Go作为脚本语言的潜力</h2>
<p>在传统的DevOps任务中，Python和Shell脚本长期以来都是主力军，它们(尤其是Python)以其简洁的语法和丰富的生态系统赢得了DevOps社区的广泛青睐。然而，传统主力Python和Shell脚本虽然灵活易用，但在处理大规模数据或需要高性能的场景时往往力不从心。此外，它们的动态类型系统可能导致运行时错误，增加了调试难度。</p>
<p>随着Go的普及，它的“超高性价比”逐渐被开发运维人员所接受：<strong>既有着接近于脚本语言的较低的学习曲线与较高的生产力(也得益于Go超快的编译速度)，又有着静态语言的高性能，还有单一文件在部署方面的便利性</strong>。</p>
<p>下面是一个简单的文件处理脚本，用于向大家展示Go的简单易学：</p>
<pre><code>package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    file, err := os.Open("input.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.Contains(line, "ERROR") {
            fmt.Println(line)
        }
    }
}
</code></pre>
<p>这个示例虽然要比同等功能的Python或shell代码行数要多，但由于Go的简单和直观，多数人都很容易看懂这段代码。</p>
<p>此外，Go的静态强类型系统可以在编译时捕获更多错误，避免在运行时的调试，提高了脚本在运行时的可靠性。</p>
<p>开发运维人员眼中的脚本语言，如Shell脚本和Python脚本，通常是直接基于源代码进行解释和运行的。实际上，Go语言同样可以实现这一点，而其关键工具就是go run命令。这个命令允许开发者快速执行Go代码，从而使Go源码看起来更像是“脚本”，下面我们就来看看go run。</p>
<h2>3. go run：桥接编译型语言与脚本语言的利器</h2>
<p>我们知道go run命令实际上是编译和运行的组合，它<strong>首先编译源代码，然后立即执行生成的二进制文件</strong>。这个过程对用户来说是透明的，使得Go程序可以像脚本一样方便地运行。这一命令也大大简化了Go程序的开发流程，使Go更接近传统的脚本语言工作流。可以说，通过go run，Go语言向脚本语言的使用体验更靠近了一步。</p>
<p>此外，go run与go build在编译阶段的行为并不完全相同：</p>
<ul>
<li>
<p>go run在运行结束后，不保留编译后的二进制文件；而go build生成可执行文件并保留。</p>
</li>
<li>
<p>go run编译时<strong>默认不包含调试信息，以减少构建时间</strong>；而go build则保留完整的调试信息。</p>
</li>
<li>
<p>go run可以使用-exec标志指定运行环境，比如：</p>
</li>
</ul>
<pre><code>$go run -exec="ls" main.go
/var/folders/cz/sbj5kg2d3m3c6j650z0qfm800000gn/T/go-build1742641170/b001/exe/main
</code></pre>
<p>我们看到，如果设置了-exec标志，那么go run -exec=”prog” main.go args编译后的命令执行就变为了”prog a.out args”。go run还支持跨平台模拟执行，当GOOS或GOARCH与系统默认值不同时，如果在\$PATH路径下存在名为”go_\$GOOS_\$GOARCH_exec”的程序，那么go run就会执行：</p>
<pre><code>$go_$GOOS_$GOARCH_exec a.out args

比如：go_js_wasm_exec a.out args
</code></pre>
<ul>
<li>
<p>go run通常用于运行main包，在go module开启的情况下，go run使用的是main module的上下文。go build可以编译多个包，对于非main包时只检查构建而不生成输出</p>
</li>
<li>
<p>go run还支持运行一个指定版本号的包</p>
</li>
</ul>
<p>当指定了版本后缀（如@v1.0.0或@latest）时，go run会进入module-aware mode（模块感知模式），并忽略当前目录或上级目录中的go.mod文件。这意味着，即使你当前的项目中存在依赖管理文件go.mod，go run也不会影响或修改当前项目的依赖关系，下面这个示例展示了这一点：</p>
<pre><code>$go run golang.org/x/example/hello@latest

go: downloading golang.org/x/example v0.0.0-20240925201653-1a5e218e5455
go: downloading golang.org/x/example/hello v0.0.0-20240925201653-1a5e218e5455
Hello, world!
</code></pre>
<p>这个功能特别适合在不影响主模块依赖的情况下，临时运行某个工具或程序。例如，如果你只是想测试某个工具的特定版本，或者快速运行一个远程程序包，而不希望它干扰你正在开发的项目中的依赖项，这种方式就很实用。</p>
<p>不过有一点要注意的是：go run的退出状态并不等于编译后二进制文件的退出状态，看下面这个示例：</p>
<pre><code>// main.go成功退出
$go run main.go
Hello from myapp!
$echo $?
0

// main.go中调用os.Exit(2)退出
$go run main.go
Hello from myapp!
exit status 2
$echo $?
1
</code></pre>
<p>go run使用退出状态1来表示其运行程序的异常退出状态，但这个值和真实的exit的状态值不相等。</p>
<p>到这里我们看到，go run xxx.go可以像bash xxx.sh或python xxx.py那样，以“解释”方式运行一个Go源码文件。这使得Go语言在某种程度上具备了脚本语言的特性。然而，在脚本语言中，例如Bash或Python等，用户可以通过将源码文件设置为可执行，并在文件的首行添加适当的解释器指令，从而直接运行脚本，而无需显式调用解释器。这种灵活性使得脚本的执行变得更加简便。那么Go是否也可以做到这一点呢？我们继续往下看。</p>
<h2>4. Go脚本化的实现方式</h2>
<p>下面是通过一些技巧或第三方工具实现Go脚本化的方法。对于喜欢使用脚本的人来说，最熟悉的莫过于shebang（即解释器指令）。在许多脚本语言中，通过在文件的第一行添加指定的解释器路径，可以直接运行脚本，而无需显式调用解释器。例如，在Bash或Python脚本中，通常会看到这样的行：</p>
<pre><code>#!/usr/bin/env python3
</code></pre>
<p>那么Go语言支持shebang吗? 是否可以实现实现类似的效果呢？我们下面来看看。</p>
<h3>4.1 使用“shebang(#!)”运行Go脚本</h3>
<p>很遗憾，Go不能直接支持shebang，我们看一下这个示例main.go：</p>
<pre><code>#!/usr/bin/env go run 

package main

import (
    "fmt"
    "os"
)

func main() {
    s := "world"
    if len(os.Args) &gt; 1 {
        s = os.Args[1]
    }
    fmt.Printf("Hello, %v!\n", s)
}
</code></pre>
<p>这一示例的第一行就是一个shebang解释器指令，我们chmod u+x main.go，然后执行该Go“脚本”：</p>
<pre><code>$./main.go
main.go:1:1: illegal character U+0023 '#'
</code></pre>
<p>这个执行过程中，Shell可以正常识别shebang，然后调用go run去运行main.go，问题就在于go编译器视shebang这一行为非法语法！</p>
<p>常规的shebang写法行不通，我们就使用一些trick，下面是改进后的示例：</p>
<pre><code>//usr/bin/env go run $0 $@; exit

package main

import (
    "fmt"
    "os"
)

func main() {
    s := "world"
    if len(os.Args) &gt; 1 {
        s = os.Args[1]
    }
    fmt.Printf("Hello, %v!\n", s)
}
</code></pre>
<p>这段代码则可以chmod +x 后直接运行：</p>
<pre><code>$./main.go
Hello, world!
$./main.go gopher
Hello, gopher!
</code></pre>
<p>这是因为它巧妙地结合了shell脚本和Go代码的特性。我们来看一下第一行：</p>
<pre><code>//usr/bin/env go run $0 $@; exit
</code></pre>
<p>这一行看起来像是Go的注释，但实际上是一个shell命令。当文件被执行时，shell会解释这一行，/usr/bin/env用于寻找go命令的路径，go run \$0 \$@ 告诉go命令运行当前脚本文件(\$0)以及所有传递给脚本的参数(\$@)，当go run编译这个脚本时，又会将第一行当做注释行而忽略，这就是关键所在。最后的exit确保shell在Go程序执行完毕后退出。如果没有exit，shell会执行后续Go代码，那显然会导致报错！</p>
<p>除了上述trick外，我们还可以将Go源码文件注册为可执行格式(仅在linux上进行了测试)，下面就是具体操作步骤。</p>
<h3>4.2 在Linux系统中注册Go为可执行格式</h3>
<p>就像在Windows上双击某个文件后，系统打开特定程序处理对应的文件一样，我们也可以将Go源文件(xxx.go)注册为可执行格式，并指定用于处理该文件的程序。实现这一功能，我们需要借助binfmt_misc。binfmt_misc是Linux内核的一个功能，允许用户注册新的可执行文件格式。这使得Linux系统能够识别并执行不同类型的可执行文件，比如脚本、二进制文件等。</p>
<p>我们用下面命令将Go源文件注册到binfmt_misc中：</p>
<pre><code>echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register
</code></pre>
<p>简单解释一下上述命令：</p>
<ul>
<li>:golang:：这是注册的格式的名称，可以自定义。</li>
<li>E::：表示执行文件的魔数（magic number），在这里为空，表示任何文件类型。</li>
<li>go::：指定用于执行的解释器，这里是go命令。</li>
<li>/usr/local/bin/gorun：指定用于执行的程序路径，这里是一个自定义的gorun脚本</li>
<li>:OC：表示这个格式是可执行的（O）并且支持在运行时创建（C）。</li>
</ul>
<p>当你执行一个Go源文件时，Linux内核会检查文件的类型。如果文件的格式与注册的格式匹配，内核会调用指定的解释器（在这个例子中是gorun）来执行该文件。</p>
<p>gorun脚本是我们自己编写的，源码如下：</p>
<pre><code>#!/bin/bash

# 检查是否提供了源文件
if [ -z "$1" ]; then
  echo "用法: gorun &lt;go源文件&gt; [参数...]"
  exit 1
fi

# 检查文件是否存在
if [ ! -f "$1" ]; then
  echo "错误: 文件 $1 不存在"
  exit 1
fi

# 将第一个参数作为源文件，剩余的参数作为执行参数
GO_FILE="$1"
shift  # 移除第一个参数，剩余的参数将会被传递

# 使用go run命令执行Go源文件，传递其余参数
go run "$GO_FILE" "$@"
</code></pre>
<p>将gorun脚本放置带/usr/local/bin下，并chmod +x使其具有可执行权限。</p>
<p>接下来，我们就可以直接执行不带有”shebang”的正常go源码了：</p>
<pre><code>// main.go
package main

import (
    "fmt"
    "os"
)

func main() {
      s := "world"
      if len(os.Args) &gt; 1 {
          s = os.Args[1]
      }
      fmt.Printf("Hello, %v!\n", s)
}
</code></pre>
<p>直接执行上述源文件：</p>
<pre><code>$ ./main.go
Hello, world!
$ ./main.go gopher
Hello, gopher!
</code></pre>
<h3>4.3 第三方工具支持</h3>
<p>Go社区也有一些将支持将Go源文件视为脚本的解释器工具，比如：<a href="https://github.com/traefik/yaegi">traefik/yaegi</a>等。</p>
<pre><code>$go install github.com/traefik/yaegi/cmd/yaegi@latest
go: downloading github.com/traefik/yaegi v0.16.1
$yaegi main.go
Hello, main.go!
</code></pre>
<p>yaegi还可以像python那样，提供Read-Eval-Print-Loop功能，我们可以与yaegi配合进行交互式“Go脚本”编码：</p>
<pre><code>$ yaegi
&gt; 1+2
: 3
&gt; import "fmt"
: 0xc0003900d0
&gt; fmt.Println("hello, golang")
hello, golang
: 14
&gt;
</code></pre>
<p>类似的提供REPL功能的第三方Go解释器还包括：<a href="https://github.com/cosmos72/gomacro">cosmos72/gomacro</a>、<a href="https://github.com/x-motemen/gore">x-motemen/gore</a>等，这里就不深入介绍了，感兴趣的童鞋可以自行研究。</p>
<h2>5. 小结</h2>
<p>在本文中，我们探讨了Go语言在DevOps和日常脚本编写中的多面性。首先，Go语言因其高性能、并发处理能力及跨平台编译特性，成为DevOps领域的重要工具，助力于自动化任务和微服务部署。其次，随着Go语言的普及，其作为脚本语言的潜力逐渐被开发运维人员认识，Go展现出了优于传统脚本语言的高效性和可靠性。</p>
<p>我们还介绍了Go脚本的实现方式，包括使用go run命令，它使得Go程序的执行更像传统脚本语言，同时也探讨了一些技巧和工具，帮助开发者将Go源码文件作为可执行脚本直接运行。通过这些探索，我们可以看到Go语言在现代开发中的灵活应用及其日益增长的吸引力。</p>
<p>随着AI能力的飞速发展，使用Go编写一个日常脚本就是分分钟的事情，但Go的特性让这样的脚本具备了传统脚本语言所不具备的并发性、可靠性和性能优势。我们有理由相信，Go在DevOps和脚本编程领域的应用将会越来越广泛，为开发者带来更多的可能性和便利。</p>
<h2>6. 参考资料</h2>
<ul>
<li><a href="https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/">Using Go as a scripting language in Linux</a> &#8211; https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/</li>
<li><a href="https://www.infoq.com/news/2020/04/go-scripting-language/">Go as a Scripting Language</a> &#8211; https://www.infoq.com/news/2020/04/go-scripting-language/</li>
<li><a href="https://utcc.utoronto.ca/~cks/space/blog/sysadmin/SysadminGoVsPython">Go compared to Python for small scale system administration scripts and tools</a> &#8211; https://utcc.utoronto.ca/~cks/space/blog/sysadmin/SysadminGoVsPython</li>
</ul>
<hr />
<p><a href="https://public.zsxq.com/groups/51284458844544">Gopher部落知识星球</a>在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时，我们也会加强代码质量和最佳实践的分享，包括如何编写简洁、可读、可测试的Go代码。此外，我们还会加强星友之间的交流和互动。欢迎大家踊跃提问，分享心得，讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落，享受coding的快乐! 欢迎大家踊跃加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/go-programming-from-beginner-to-master-qr.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /></p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻) &#8211; https://gopherdaily.tonybai.com</p>
<p>我的联系方式：</p>
<ul>
<li>微博(暂不可用)：https://weibo.com/bigwhite20xx</li>
<li>微博2：https://weibo.com/u/6484441286</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>Gopher Daily归档 &#8211; https://github.com/bigwhite/gopherdaily</li>
<li>Gopher Daily Feed订阅 &#8211; https://gopherdaily.tonybai.com/feed</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2024, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2024/10/08/go-languages-versatility-from-devops-to-daily-scripts/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>使用Docker Compose构建一键启动的运行环境</title>
		<link>https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose/</link>
		<comments>https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose/#comments</comments>
		<pubDate>Fri, 26 Nov 2021 13:08:10 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[container]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[docker-compose]]></category>
		<category><![CDATA[docker-compose.yml]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[jaeger]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[Kafka]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Make]]></category>
		<category><![CDATA[Makefile]]></category>
		<category><![CDATA[microservice]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[nacos]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[profiling]]></category>
		<category><![CDATA[prometheus]]></category>
		<category><![CDATA[pyroscope]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[swarm]]></category>
		<category><![CDATA[Trace]]></category>
		<category><![CDATA[yaml]]></category>
		<category><![CDATA[云原生]]></category>
		<category><![CDATA[容器]]></category>
		<category><![CDATA[微服务]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=3345</guid>
		<description><![CDATA[本文永久链接 &#8211; https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose 如今，不管你是否喜欢，不管你是否承认，微服务架构模式的流行就摆在那里。作为架构师的你，如果再将系统设计成个大单体结构，那么即便不懂技术的领导，都会给你送上几次白眼。好吧，妥协了！开拆！“没吃过猪肉，还没见过猪跑吗！”。拆不出40-50个服务，我就不信还拆不出4-5个服务^_^。 终于拆出了几个服务，但又犯难了：以前单体程序，搭建一个运行环境十分easy，程序往一个主机上一扔，配置配置，启动就ok了；但自从拆成服务后，开发人员的调试环境、集成环境、测试环境等搭建就变得异常困难。 有人会说，现在都云原生了？你不知道云原生操作系统k8s的存在么？让运维帮你在k8s上整环境啊。 一般小厂，运维人员不多且很忙，开发人员只能“自力更生，丰衣足食”。开发人员自己整k8s？别扯了！没看到这两年k8s变得越来越复杂了吗！如果有一年不紧跟k8s的演进，新版本中的概念你就可能很陌生，不知源自何方。一般开发人员根本搞不定(如果你想搞定，可以看看我的k8s实战课程哦，包教包会^_^)。 那怎么办呢？角落里曾经的没落云原生贵族docker发话了：要不让我兄弟试试！ 1. docker compose docker虽然成了“过气网红”，但docker依然是容器界的主流。至少对于非docker界的开发人员来说，一提到容器，大家首先想到的还是docker。 docker公司的产品推出不少，开发人员对多数都不买账也是现实，但我们也不能一棒子打死，毕竟docker是可用的，还有一个可用的，那就是docker的兄弟：docker compose。 Compose是一个用于定义和运行多容器Docker应用程序的工具。使用Compose，我们可以使用一个YAML文件来配置应用程序的所有服务组件。然后，只需一条命令，我们就可以创建并启动配置中的所有服务。 这不正是我们想要的工具么! Compose与k8s很像，都算是容器编排工具，最大的不同：Compose更适合在单节点上的调试或集成环境中（虽然也支持跨主机，基于被淘汰的docker swarm)。Compose可以大幅提升开发人员以及测试人员搭建应用运行环境的效率。 2. 选版本 使用docker compose搭建运行环境，我们仅需一个yml文件。但docker compose工具也经历了多年演化，这个文件的语法规范也有多个版本，截至目前，docker compose的配置文件的语法版本就有2、2.x和3.x三种。并且不同规范版本支持的docker引擎版本还不同，这个对应关系如下图。图来自docker compose文件规范页面： 选版本是最闹心的。选哪个呢？设定两个条件： docker引擎版本怎么也得是17.xx 规范版本怎么也得是3.x吧 这样一来，版本3.2是最低要求的了。我们就选3.2： // docker-compose.yml version: "3.2" 3. 选网络 docker compose默认会为docker-compose.yml中的各个service创建一个bridge网络，所有service在这个网络里可以相互访问。以下面docker-compose.yml为例： // demo1/docker-compose.yml version: "3.2" services: srv1: image: nginx:latest container_name: srv1 srv2: image: nginx:latest container_name: srv2 启动这个yml中的服务： # docker-compose [...]]]></description>
			<content:encoded><![CDATA[<p><img src="https://tonybai.com/wp-content/uploads/build-all-in-one-runtime-environment-with-docker-compose-1.png" alt="" /></p>
<p><a href="https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose">本文永久链接</a> &#8211; https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose</p>
<p>如今，不管你是否喜欢，不管你是否承认，微服务架构模式的流行就摆在那里。作为架构师的你，如果再将系统设计成个大单体结构，那么即便不懂技术的领导，都会给你送上几次白眼。好吧，妥协了！开拆！“没吃过猪肉，还没见过猪跑吗！”。拆不出40-50个服务，我就不信还拆不出4-5个服务^_^。</p>
<p>终于拆出了几个服务，但又犯难了：以前单体程序，搭建一个运行环境十分easy，程序往一个主机上一扔，配置配置，启动就ok了；但自从拆成服务后，开发人员的调试环境、集成环境、测试环境等搭建就变得异常困难。</p>
<p>有人会说，现在都云原生了？你不知道<a href="https://kubernetes.io">云原生操作系统k8s</a>的存在么？让运维帮你在k8s上整环境啊。 一般小厂，运维人员不多且很忙，开发人员只能“自力更生，丰衣足食”。开发人员自己整k8s？别扯了！没看到这两年k8s变得越来越复杂了吗！如果有一年不紧跟k8s的演进，新版本中的概念你就可能很陌生，不知源自何方。一般开发人员根本搞不定(如果你想搞定，可以看看<a href="https://coding.imooc.com/class/284.html">我的k8s实战课程</a>哦，包教包会^_^)。</p>
<p>那怎么办呢？角落里<strong>曾经的没落云原生贵族docker</strong>发话了：要不让我兄弟试试！</p>
<h3>1. docker compose</h3>
<p><a href="https://tonybai.com/2017/12/21/the-concise-history-of-docker-image-building">docker</a>虽然成了“过气网红”，但docker依然是容器界的主流。至少对于非docker界的开发人员来说，一提到容器，大家首先想到的还是docker。</p>
<p>docker公司的产品推出不少，开发人员对多数都不买账也是现实，但我们也不能一棒子打死，毕竟docker是可用的，还有一个可用的，那就是docker的兄弟：<a href="https://docs.docker.com/compose/">docker compose</a>。</p>
<p>Compose是一个用于定义和运行多容器Docker应用程序的工具。使用Compose，我们可以使用一个<a href="https://tonybai.com/2019/02/25/introduction-to-yaml-creating-a-kubernetes-deployment/">YAML文件</a>来配置应用程序的所有服务组件。然后，只需一条命令，我们就可以创建并启动配置中的所有服务。</p>
<p><strong>这不正是我们想要的工具么</strong>! Compose与k8s很像，都算是容器编排工具，最大的不同：Compose更适合在单节点上的调试或集成环境中（虽然也支持跨主机，基于被淘汰的docker swarm)。Compose可以大幅提升开发人员以及测试人员搭建应用运行环境的效率。</p>
<h3>2. 选版本</h3>
<p>使用docker compose搭建运行环境，我们仅需一个yml文件。但docker compose工具也经历了多年演化，这个文件的语法规范也有多个版本，截至目前，docker compose的配置文件的语法版本就有2、2.x和3.x三种。并且不同规范版本支持的docker引擎版本还不同，这个对应关系如下图。图来自<a href="https://docs.docker.com/compose/compose-file/">docker compose文件规范页面</a>：</p>
<p><img src="https://tonybai.com/wp-content/uploads/build-all-in-one-runtime-environment-with-docker-compose-2.png" alt="" /></p>
<p>选版本是最闹心的。选哪个呢？设定两个条件：</p>
<ul>
<li>docker引擎版本怎么也得是17.xx</li>
<li>规范版本怎么也得是3.x吧</li>
</ul>
<p>这样一来，版本3.2是最低要求的了。我们就选3.2：</p>
<pre><code>// docker-compose.yml
version: "3.2"
</code></pre>
<h3>3. 选网络</h3>
<p>docker compose默认会为docker-compose.yml中的各个service创建一个bridge网络，所有service在这个网络里可以相互访问。以下面docker-compose.yml为例：</p>
<pre><code>// demo1/docker-compose.yml
version: "3.2"
services:
  srv1:
    image: nginx:latest
    container_name: srv1
  srv2:
    image: nginx:latest
    container_name: srv2
</code></pre>
<p>启动这个yml中的服务：</p>
<pre><code># docker-compose -f docker-compose.yml up -d
Creating network "demo1_default" with the default driver
... ...
</code></pre>
<p>docker compose会为这组容器创建一个名为demo1_default的桥接网络:</p>
<pre><code># docker network ls
NETWORK ID          NAME                     DRIVER              SCOPE
f9a6ac1af020        bridge                   bridge              local
7099c68b39ec        demo1_default            bridge              local
... ...
</code></pre>
<p>关于demo1_default网络的细节，可以通过docker network inspect 7099c68b39ec获得。</p>
<p>对于这样的网络中的服务，我们在外部是无法访问的。如果要访问其中服务，我们需要对其中的服务做端口映射，比如如果我们要将srv1暴露到外部，我们可以将srv1监听的服务端口80映射到主机上的某个端口，这里用8080，修改后的docker-compose.yml如下：</p>
<pre><code>version: "3.2"
services:
  srv1:
    image: nginx:latest
    container_name: srv1
    ports:
    - "8080:80"
  srv2:
    image: nginx:latest
    container_name: srv2
</code></pre>
<p>这样启动该组容器后，我们通过curl localhost:8080就可以访问到容器中的srv1服务。不过这种情况下，服务间的相互发现比较麻烦，要么借助于外部的发现服务，要么通过容器间的link来做。</p>
<p>开发人员大多只有一个环境，不同服务的服务端口亦不相同，让容器使用host网络要比单独创建一个bridge网络来的更加方便。通过network_mode我们可以指定服务使用host网络，就像下面这样：</p>
<pre><code>version: "3.2"
services:
  srv1:
    image: bigwhite/srv1:1.0.0
    container_name: srv1
    network_mode: "host"
</code></pre>
<p>在host网络下，容器监听的端口就是主机上的端口，各个服务间通过端口区别各个服务实例(前提是端口各不相同)，ip使用localhost即可。</p>
<p>使用host网络还有一个好处，那就是我们在该环境之外的主机上访问环境中的服务也十分方便，比如查看prometheus的面板等。</p>
<h3>4. 依赖的中间件先启动，预置配置次之</h3>
<p>如今的微服务架构系统，除了自身实现的服务外，外围还有大量其依赖的中间件，比如：redis、kafka(mq)、nacos/etcd(服务发现与注册）、prometheus(时序度量数据服务)、mysql(关系型数据库)、jaeger server(trace服务器)、elastic(日志中心)、pyroscope-server(持续profiling服务)等。</p>
<p>这些中间件若没有启动成功，我们自己的服务多半启动都要失败，因此我们要保证这些中间件服务都启动成功后，再来启动我们自己的服务。</p>
<p>如何做呢？compose规范中有一个<a href="https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on">迷惑人的“depends_on”</a>，比如下面配置文件中srv1依赖redis和nacos两个service：</p>
<pre><code>version: "3.2"
services:
  srv1:
    image: bigwhite/srv1:1.0.0
    container_name: srv1
    network_mode: "host"
    depends_on:
      - "redis"
      - "nacos"
    environment:
      - NACOS_SERVICE_ADDR=127.0.0.1:8848
      - REDIS_SERVICE_ADDR=127.0.0.1:6379
    restart: on-failure
</code></pre>
<p>不深入了解，很多人会认为depends_on可以保证先启动依赖项redis和nacos，并等依赖项ready后再启动我们自己的服务srv1。但实际上，depends_on仅能保证先启动依赖项，后启动我们的服务。但它不会探测依赖项redis或nacos是否ready，也不会等依赖项ready后，才启动我们的服务。于是你会看到srv1启动后依旧出现各种的报错，包括无法与redis、nacos建立连接等。</p>
<p>要想真正实现依赖项ready后才启动我们自己的服务，我们需要借助外部工具了，<a href="https://docs.docker.com/compose/startup-order/">docker compose文档对此有说明</a>。其中一个方法是使用<a href="https://github.com/vishnubob/wait-for-it">wait-for-it脚本</a>。</p>
<p>我们可以改变一下自由服务的容器镜像，将其entrypoint从执行服务的可执行文件变为执行一个start.sh的脚本：</p>
<pre><code>// Dockerfile
... ...
ENTRYPOINT ["/bin/bash", "./start.sh"]

</code></pre>
<p>这样我们就可以在start.sh脚本中“定制”我们的启动逻辑了。下面是一个start.sh脚本的示例：</p>
<pre><code>#! /bin/sh

./wait_for_it.sh $NACOS_SERVICE_ADDR -t 60 --strict -- echo "nacos is up" &amp;&amp; \
./wait_for_it.sh $REDIS_SERVICE_ADDR -- echo "redis is up" &amp;&amp; \
exec ./srv1
</code></pre>
<p>我们看到，在start.sh脚本中，我们使用<a href="https://github.com/vishnubob/wait-for-it/blob/master/wait-for-it.sh">wait_for_it.sh脚本</a>等待nacos和redis启动，如果在限定时间内等待失败，根据restart策略，我们的服务还会被docker compose重新拉起，直到nacos与redis都ready，我们的服务才会真正开始执行启动过程。</p>
<p>在exec ./srv1之前，很多时候我们还需要进行一些配置初始化操作，比如向nacos中写入预置的srv1服务的配置文件内容以保证srv1启动后能从nacos中读取到自己的配置文件，下面是加了配置初始化的start.sh：</p>
<pre><code>#! /bin/sh

./wait_for_it.sh $NACOS_SERVICE_ADDR -t 60 --strict -- echo "nacos is up" &amp;&amp; \
./wait_for_it.sh $REDIS_SERVICE_ADDR -- echo "redis is up" &amp;&amp; \
curl -X POST --header 'Content-Type: application/x-www-form-urlencoded' -d dataId=srv1.yml --data-urlencode content@./conf/srv1.yml "http://127.0.0.1:8848/nacos/v1/cs/configs?group=MY_GROUP" &amp;&amp; \
exec ./srv1
</code></pre>
<p>我们通过curl将打入镜像的./conf/srv1.yml配置写入已经启动了的nacos中供后续srv1启动时读取。</p>
<h3>5. 全家桶，一应俱全</h3>
<p>就像前面提到的，如今的系统对外部的中间件“依存度”很高，好在主流中间件都提供了基于docker启动的官方支持。这样我们的开发环境也可以是一个一应俱全的“全家桶”。不过要有一个很容易满足的前提：你的机器配置足够高，才能把这些中间件全部运行起来。</p>
<p>有了这些全家桶，我们无论是诊断问题(看log、看trace、看度量数据），还是作性能优化（看持续profiling的数据），都方便的不要不要的。</p>
<h3>6. 结合Makefile，简化命令行输入</h3>
<p>docker-compose这个工具有一个“严重缺陷”，那就是名字太长^_^。这导致我们每次操作都要敲入很多命令字符，当你使用的compose配置文件名字不为docker-compose.yml时，更是如此，我们还需要通过-f选项指定配置文件路径。</p>
<p>为了简化命令行输入，减少键盘敲击次数，我们可以将复杂的docker-compose命令与Makefile相结合，通过定制命令行命令并将其赋予简单的make target名字来实现这一简化目标，比如：</p>
<pre><code>// Makefile

pull:
    docker-compose -f my-docker-compose.yml pull

pull-my-system:
    docker-compose -f my-docker-compose.yml pull srv1 srv2 srv3

up: pull-my-system
    docker-compose -f my-docker-compose.yml up

upd: pull-my-system
    docker-compose -f my-docker-compose.yml up -d

up2log: pull-my-system
    docker-compose -f my-docker-compose.yml up &gt; up.log 2&gt;&amp;1

down:
    docker-compose -f my-docker-compose.yml down

ps:
    docker-compose -f my-docker-compose.yml ps -a

log:
    docker-compose -f my-docker-compose.yml logs -f

# usage example: make upsrv service=srv1
service=
upsrv:
    docker-compose -f my-docker-compose.yml up -d ${service}

config:
    docker-compose -f my-docker-compose.yml config
</code></pre>
<p>另外服务依赖的中间件一般都时启动与运行开销较大的系统，每次和我们的服务一起启停十分浪费时间，我们可以将这些依赖与我们的服务分别放在不同的compose配置文件中管理，这样我们每次重启自己的服务时，没有必要重新启动这些依赖，这样可以节省大量“等待”时间。</p>
<h3>7. .env文件</h3>
<p>有些时候，我们需要在compose的配置文件中放置一些“变量”，我们通常使用环境变量来实现“变量”的功能，比如：我们将srv1的镜像版本改为一个环境变量：</p>
<pre><code>version: "3.2"
services:
  srv1:
    image: bigwhite/srv1:${SRV1_VER}
    container_name: srv1
    network_mode: "host"
  ... ...
</code></pre>
<p>docker compose支持通过同路径下的.env文件的方式docker-compose.yml中环境变量的值，比如：</p>
<pre><code>// .env
SRV1_VER=dev
</code></pre>
<p>这样docker compose在启动srv1时会将.env中SRV1_VER的值读取出来并替换掉compose配置文件中的相应环境变量。通过这种方式，我们可以灵活的修改我们使用的镜像版本。</p>
<h3>8. 优点与不足</h3>
<p>使用docker compose工具，我们可以轻松拥有并快速启动一个all-in-one的运行环境，大幅度加速了部署、调试与测试的效率，在特定的工程环节，它可以给予开发与测试人员很大帮助。</p>
<p>不过这样的运行环境也有一些不足，比如：</p>
<ul>
<li>对部署的机器/虚拟机配置要求较高；</li>
<li>这样的运行环境有局限，用在功能测试、持续集成、验收测试的场景下可以，但不能用来执行压测或者说即便压测也只是摸底，数据不算数的，因为所有服务放在一起，相互干扰；</li>
<li>服务或中间件多了以后，完全启动一次也要耐心等待一段时间。</li>
</ul>
<hr />
<p><a href="https://mp.weixin.qq.com/s/jUqAL7hf2GmMun64BJufEA">“Gopher部落”知识星球</a>正式转正（从试运营星球变成了正式星球）！“gopher部落”旨在打造一个精品Go学习和进阶社群！高品质首发Go技术文章，“三天”首发阅读权，每年两期Go语言发展现状分析，每天提前1小时阅读到新鲜的Gopher日报，网课、技术专栏、图书内容前瞻，六小时内必答保证等满足你关于Go语言生态的所有需求！部落目前虽小，但持续力很强，欢迎大家加入！</p>
<p><img src="http://image.tonybai.com/img/tonybai/gopher-tribe-zsxq-small-card.png" alt="img{512x368}" /></p>
<p><img src="http://image.tonybai.com/img/tonybai/go-first-course-banner.png" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-go-column-pgo-with-qr.jpg" alt="img{512x368}" /><br />
<img src="http://image.tonybai.com/img/tonybai/imooc-k8s-practice-with-qr.jpg" alt="img{512x368}" /></p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/。smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。2020年4月8日，中国三大电信运营商联合发布《5G消息白皮书》，51短信平台也会全新升级到“51商用消息平台”，全面支持5G RCS消息。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>Gopher Daily(Gopher每日新闻)归档仓库 &#8211; https://github.com/bigwhite/gopherdaily</p>
<p>我的联系方式：</p>
<ul>
<li>微博：https://weibo.com/bigwhite20xx</li>
<li>微信公众号：iamtonybai</li>
<li>博客：tonybai.com</li>
<li>github: https://github.com/bigwhite</li>
<li>“Gopher部落”知识星球：https://public.zsxq.com/groups/51284458844544</li>
</ul>
<p><img src="http://image.tonybai.com/img/tonybai/iamtonybai-wechat-qr.png" alt="" /></p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2021, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2021/11/26/build-all-in-one-runtime-environment-with-docker-compose/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>提高您的kubectl生产力（第三部分）：集群上下文切换、使用别名减少输入和插件扩展</title>
		<link>https://tonybai.com/2019/08/31/kubectl-productivity-part3/</link>
		<comments>https://tonybai.com/2019/08/31/kubectl-productivity-part3/#comments</comments>
		<pubDate>Sat, 31 Aug 2019 05:01:43 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[alias]]></category>
		<category><![CDATA[apiserver]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[bashrc]]></category>
		<category><![CDATA[bash_profile]]></category>
		<category><![CDATA[brew]]></category>
		<category><![CDATA[complete-alias]]></category>
		<category><![CDATA[Context]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[fzf]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[jsonpath]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[krew]]></category>
		<category><![CDATA[kube-controller-manager]]></category>
		<category><![CDATA[kube-scheduler]]></category>
		<category><![CDATA[kubeconfig]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubectl-aliases]]></category>
		<category><![CDATA[kubectl-ctx]]></category>
		<category><![CDATA[kubectl-ns]]></category>
		<category><![CDATA[kubectx]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[kubens]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[Namespace]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[ReplicaSet]]></category>
		<category><![CDATA[RESTAPI]]></category>
		<category><![CDATA[RESTFUL]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[xpath]]></category>
		<category><![CDATA[yaml]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[zsh]]></category>
		<category><![CDATA[上下文]]></category>
		<category><![CDATA[命名空间]]></category>
		<category><![CDATA[插件]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2757</guid>
		<description><![CDATA[本文翻译自《Boosting your kubectl productivity》。 第一部分：什么是kubectl？ 第二部分：命令完成、资源规范快速查看和自定义列输出格式什么是kubectl？ 4. 轻松切换集群和名称空间 当kubectl必须向Kubernetes API发出请求时，它会读取系统上所谓的kubeconfig文件，以获取它需要访问的所有连接参数并向API服务器发出请求。 默认的kubeconfig文件是~/.kube/config。此文件通常由某个命令自动创建或更新（例如，aws eks update-kubeconfig或者gcloud container clusters get-credentials，如果您使用托管Kubernetes服务）。 使用多个集群时，您的kubeconfig文件中配置了多个集群的连接参数。这意味着，您需要一种方法来告诉kubectl 您希望它连接到哪个集群。 在集群中，您可以设置多个名称空间（名称空间是物理集群中的一种“虚拟”集群）。Kubectl也会从kubeconfig文件确定用于请求的命名空间。因此，您需要一种方法来告诉kubectl 您希望它使用哪个命名空间。 本节将介绍kubectl切换集群上下文的原理以及它是如何轻松完成的。 请注意，您还可以在KUBECONFIG环境变量中列出多个kubeconfig文件。在这种情况下，所有这些文件将在执行时合并为单个有效配置。您还可以使用&#8211;kubeconfig指定kubectl命令的选项以覆盖默认的kubeconfig文件。请参阅官方文档。 Kubeconfig文件 让我们看看kubeconfig文件实际包含的内容： 如您所见，kubeconfig文件由一组上下文组成。上下文包含以下三个元素： 集群(cluster)：集群的API服务器的URL 用户(user)：集群的特定用户的身份验证凭据 命名空间(namespace)：连接到集群时使用的命名空间 实际上，人们经常在他们的kubeconfig文件中为每个集群的配置一个上下文。但是，你也可以为每个集群配置多个上下文，其用户或命名空间不同。但这似乎不太常见，因此通常在集群和上下文之间存在一对一的映射。 在任何给定时间，其中一个上下文被设置为当前上下文（通过kubeconfig文件中的专用字段）： 当kubectl读取kubeconfig文件时，它总是使用当前上下文中的信息。因此，在上面的例子中，kubectl将连接到Hare集群。 因此，要切换到另一个集群，您只需更改kubeconfig文件中的当前上下文： 在上面的示例中，kubectl现在将连接到Fox集群。 要切换到同一集群中的另一个命名空间，您可以更改当前上下文的命名空间元素的值： 在上面的示例中，kubectl现在将使用Fox群集中的Prod命名空间（而不是之前设置的Test命名空间）。 请注意，kubectl还提供了&#8211;cluster，&#8211;user和&#8211;namespace，以及&#8211;context允许您覆盖单个元素和当前上下文本身的选项，无论kubeconfig文件中设置了什么。见kubectl options。 理论上，您可以通过手动编辑kubeconfig文件来执行这些更改。但当然这很乏味。以下部分介绍了允许您自动执行这些更改的各种工具。 使用kubectx kubectx是一种非常流行的用于在集群和命名空间之间切换的工具。 此工具提供允许您分别更改当前上下文和命名空间的命令kubectx和kubens命令。 如上所述，如果每个集群只有一个上下文，则更改当前上下文意味着更改集群。 在这里，您可以看到这两个命令： 在表象之下，这些命令只是编辑kubeconfig文件，如上一节中所述。 要安装kubectx，只需按照GitHub页面上的说明操作即可。 kubectx和kubens都通过完成交办提供命令完成(command completion)。这允许您自动完成上下文名称和名称空间，这样您就不必完全键入它们。您也可以在GitHub页面上找到设置完成的说明。 kubectx的另一个有用功能是交互模式。这与fzf工具结合使用，您必须单独安装（事实上，安装fzf，将自动启用kubectx交互模式）。交互模式允许您通过交互式模糊搜索界面（由fzf提供）选择目标上下文或命名空间。 使用shell别名 实际上，您并不需要单独的工具来更改当前上下文和命名空间，因为kubectl也提供了执行此操作的命令。特别是，该kubectl config命令提供了用于编辑kubeconfig文件的子命令。这里是其中的一些： kubectl config get-contexts：列出所有上下文 kubectl [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自<a href="https://learnk8s.io/blog/kubectl-productivity/">《Boosting your kubectl productivity》</a>。</p>
<p>第一部分：<a href="https://tonybai.com/2019/08/29/kubectl-productivity-part1/">什么是kubectl？</a><br />
第二部分：<a href="https://tonybai.com/2019/08/30/kubectl-productivity-part2/">命令完成、资源规范快速查看和自定义列输出格式什么是kubectl？</a></p>
<h2>4. 轻松切换集群和名称空间</h2>
<p>当kubectl必须向<a href="https://tonybai.com/tag/k8s">Kubernetes</a> API发出请求时，它会读取系统上所谓的kubeconfig文件，以获取它需要访问的所有连接参数并向API服务器发出请求。</p>
<blockquote>
<p>默认的kubeconfig文件是~/.kube/config。此文件通常由某个命令自动创建或更新（例如，aws eks update-kubeconfig或者gcloud container clusters get-credentials，如果您使用托管Kubernetes服务）。</p>
</blockquote>
<p>使用多个集群时，您的kubeconfig文件中配置了多个集群的连接参数。这意味着，您需要一种方法来告诉kubectl 您希望它连接到哪个集群。</p>
<p>在集群中，您可以设置多个<a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">名称空间</a>（名称空间是物理集群中的一种“虚拟”集群）。Kubectl也会从kubeconfig文件确定用于请求的命名空间。因此，您需要一种方法来告诉kubectl 您希望它使用哪个命名空间。</p>
<p>本节将介绍kubectl切换集群上下文的原理以及它是如何轻松完成的。</p>
<blockquote>
<p>请注意，您还可以在<a href="https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/#the-kubeconfig-environment-variable">KUBECONFIG环境变量</a>中列出多个kubeconfig文件。在这种情况下，所有这些文件将在执行时合并为单个有效配置。您还可以使用&#8211;kubeconfig指定kubectl命令的选项以覆盖默认的kubeconfig文件。请参阅<a href="https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/">官方文档</a>。</p>
</blockquote>
<h3>Kubeconfig文件</h3>
<p>让我们看看kubeconfig文件实际包含的内容：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-1.png" alt="img{512x368}" /></p>
<p>如您所见，kubeconfig文件由一组上下文组成。上下文包含以下三个元素：</p>
<ul>
<li>集群(cluster)：集群的API服务器的URL</li>
<li>用户(user)：集群的特定用户的身份验证凭据</li>
<li>命名空间(namespace)：连接到集群时使用的命名空间</li>
</ul>
<blockquote>
<p>实际上，人们经常在他们的kubeconfig文件中为每个集群的配置一个上下文。但是，你也可以为每个集群配置多个上下文，其用户或命名空间不同。但这似乎不太常见，因此通常在集群和上下文之间存在一对一的映射。</p>
</blockquote>
<p>在任何给定时间，其中一个上下文被设置为当前上下文（通过kubeconfig文件中的专用字段）：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-2.png" alt="img{512x368}" /></p>
<p>当kubectl读取kubeconfig文件时，它总是使用当前上下文中的信息。因此，在上面的例子中，kubectl将连接到Hare集群。</p>
<p>因此，要切换到另一个集群，您只需更改kubeconfig文件中的当前上下文：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-3.png" alt="img{512x368}" /></p>
<p>在上面的示例中，kubectl现在将连接到Fox集群。</p>
<p>要切换到同一集群中的另一个命名空间，您可以更改当前上下文的命名空间元素的值：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-4.png" alt="img{512x368}" /></p>
<p>在上面的示例中，kubectl现在将使用Fox群集中的Prod命名空间（而不是之前设置的Test命名空间）。</p>
<blockquote>
<p>请注意，kubectl还提供了&#8211;cluster，&#8211;user和&#8211;namespace，以及&#8211;context允许您覆盖单个元素和当前上下文本身的选项，无论kubeconfig文件中设置了什么。见kubectl options。</p>
</blockquote>
<p>理论上，您可以通过手动编辑kubeconfig文件来执行这些更改。但当然这很乏味。以下部分介绍了允许您自动执行这些更改的各种工具。</p>
<h3>使用kubectx</h3>
<p><a href="https://github.com/ahmetb/kubectx/">kubectx</a>是一种非常流行的用于在集群和命名空间之间切换的工具。</p>
<p>此工具提供允许您分别更改当前上下文和命名空间的命令kubectx和kubens命令。</p>
<blockquote>
<p>如上所述，如果每个集群只有一个上下文，则更改当前上下文意味着更改集群。</p>
</blockquote>
<p>在这里，您可以看到这两个命令：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-5.gif" alt="img{512x368}" /></p>
<blockquote>
<p>在表象之下，这些命令只是编辑kubeconfig文件，如上一节中所述。</p>
</blockquote>
<p>要安装kubectx，只需按照<a href="https://github.com/ahmetb/kubectx/#installation">GitHub页面上的说明操作即可</a>。</p>
<p>kubectx和kubens都通过完成交办提供命令完成(command completion)。这允许您自动完成上下文名称和名称空间，这样您就不必完全键入它们。您也可以在<a href="https://github.com/ahmetb/kubectx/#installation">GitHub页面</a>上找到设置完成的说明。</p>
<p>kubectx的另一个有用功能是<a href="https://github.com/ahmetb/kubectx/#interactive-mode">交互模式</a>。这与<a href="https://github.com/junegunn/fzf">fzf</a>工具结合使用，您必须单独安装（事实上，安装fzf，将自动启用kubectx交互模式）。交互模式允许您通过交互式模糊搜索界面（由fzf提供）选择目标上下文或命名空间。</p>
<h3>使用shell别名</h3>
<p>实际上，您并不需要单独的工具来更改当前上下文和命名空间，因为kubectl也提供了执行此操作的命令。特别是，该kubectl config命令提供了用于编辑kubeconfig文件的子命令。这里是其中的一些：</p>
<ul>
<li>kubectl config get-contexts：列出所有上下文</li>
<li>kubectl config current-context：获取当前上下文</li>
<li>kubectl config use-context：更改当前上下文</li>
<li>kubectl config set-context：更改上下文的元素</li>
</ul>
<p>但是，直接使用这些命令并不是很方便，因为它们很难输入。但是你可以做的是将它们包装成可以更容易执行的shell别名。</p>
<p>我基于这些命令创建了一组别名，这些命令提供了与kubectx类似的功能。在这里你可以看到他们的行动：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-6.gif" alt="img{512x368}" /></p>
<blockquote>
<p>请注意，别名使用fzf来提供交互式模糊搜索界面（如kubectx的交互模式）。这意味着，您需要安装fzf才能使用这些别名。</p>
</blockquote>
<p>以下是别名的定义：</p>
<pre><code># Get current context
alias krc='kubectl config current-context'
# List all contexts
alias klc='kubectl config get-contexts -o name | sed "s/^/  /;\|^  $(krc)$|s/ /*/"'
# Change current context
alias kcc='kubectl config use-context "$(klc | fzf -e | sed "s/^..//")"'

# Get current namespace
alias krn='kubectl config get-contexts --no-headers "$(krc)" | awk "{print \$5}" | sed "s/^$/default/"'
# List all namespaces
alias kln='kubectl get -o name ns | sed "s|^.*/|  |;\|^  $(krn)$|s/ /*/"'
# Change current namespace
alias kcn='kubectl config set-context --current --namespace "$(kln | fzf -e | sed "s/^..//")"'
</code></pre>
<p>要安装这些别名，你只需要在上面定义添加到您的~/.bashrc或~/.zshrc文件，并重新加载你的shell(source ~/.bashrc or source ~/.zshrc)！</p>
<h3>使用插件</h3>
<p>Kubectl允许安装可以像本机命令一样调用的插件。例如，您可以安装名为kubectl-foo的插件，然后将其调用为kubectl foo。</p>
<blockquote>
<p>Kubectl插件将在本文的后续部分中详细介绍。</p>
</blockquote>
<p>能够像这样更改当前上下文和命名空间不是很好吗？例如，运行kubectl ctx以更改上下文，kubectl ns更改名称空间？</p>
<p>我创建了两个允许这样做的插件：</p>
<ul>
<li><a href="https://github.com/weibeld/kubectl-ctx">kubectl-CTX</a></li>
<li><a href="https://github.com/weibeld/kubectl-ns">kubectl-NS</a></li>
</ul>
<p>在内部，插件构建在上一节的别名之上。</p>
<p>在这里你可以看到插件的实际效果：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-7.gif" alt="img{512x368}" /></p>
<blockquote>
<p>请注意，插件使用fzf来提供交互式模糊搜索界面。这意味着，您需要安装fzf才能使用这些插件。</p>
</blockquote>
<p>要安装插件，你只需要将名为的shell脚本<a href="https://raw.githubusercontent.com/weibeld/kubectl-ctx/master/kubectl-ctx">kubectl-ctx</a>和<a href="https://raw.githubusercontent.com/weibeld/kubectl-ns/master/kubectl-ns">kubectl-ns</a>的脚本下载以到PATH下的任何目录中，并使他们具备可执行权限（例如，使用chmod +x）。紧接着，你就应该能够使用kubectl ctx和kubectl ns！</p>
<h2>5. 使用自动生成的别名减少输入</h2>
<p>Shell别名通常是减少手工输入的好方法。该<a href="https://github.com/ahmetb/kubectl-aliases">kubectl-aliases</a>项目就是以这个想法为核心，并提供800多个kubectl命令别名。</p>
<p>您可能想知道如何记住800个别名？实际上，您不需要记住它们，因为它们都是根据一个简单的方案生成的，下面将显示一些示例别名：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-8.png" alt="img{512x368}" /></p>
<p>如您所见，别名由<strong>组件(component)</strong>组成，每个组件代表kubectl命令的特定元素。每个别名可以有一个用于基本命令，操作和资源的组件，以及用于选项的多个组件，您只需根据上述方案从左到右“填充”这些组件。</p>
<blockquote>
<p>请注意，目前完全详细的方案在<a href="https://github.com/ahmetb/kubectl-aliases#syntax-explanation">GitHub页面</a>上。在那里，您还可以找到别名的<a href="https://github.com/ahmetb/kubectl-aliases/blob/master/.kubectl_aliases">完整列表</a>。</p>
</blockquote>
<p>例如，别名kgpooyamlall代表命令kubectl get pods -o yaml &#8211;all-namespaces：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-9.png" alt="img{512x368}" /></p>
<p>请注意，大多数选项组件的相对顺序无关紧要。所以，kgpooyamlall相当于kgpoalloyaml。</p>
<p>您不需要将所有组件用于别名。例如k，kg，klo，ksys，或者kgpo是有效的别名也。此外，您可以在命令行中将别名与其他单词组合使用。</p>
<p>例如，您可以k proxy用于运行kubectl proxy：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-10.png" alt="img{512x368}" /></p>
<p>或者您可以kg roles用于运行kubectl get roles（目前不存在Roles资源的别名组件）：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-11.png" alt="img{512x368}" /></p>
<p>要获取特定Pod，您可以使用kgpo my-pod以运行kubectl get pod my-pod：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-12.png" alt="img{512x368}" /></p>
<p>请注意，某些别名甚至需要在命令行上的进一步参数。例如，kgpol别名代表kubectl get pods -l。该-l选项需要一个参数（标签规范）。所以，你必须使用这个别名，例如，像这样:</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part3-13.png" alt="img{512x368}" /></p>
<blockquote>
<p>出于这个原因，你可以使用a，f以及l只在一个别名的结尾部分。</p>
</blockquote>
<p>一般来说，一旦你掌握了这个方案，就可以直观地从你想要执行的命令中推断出别名，并节省大量的输入！</p>
<h3>安装</h3>
<p>要安装kubectl-别名，你只需要下载<a href="https://raw.githubusercontent.com/ahmetb/kubectl-aliases/master/.kubectl_aliases">.kubectl-aliases</a>GitHub文件，并在你的~/.bashrc或~/.zshrc文件生效它：</p>
<pre><code>source ~/.kubectl_aliases
</code></pre>
<p>重新加载shell后，您应该能够使用所有800个kubectl别名！</p>
<h3>命令完成</h3>
<p>如您所见，您经常在命令行上向别名添加更多单词。例如：</p>
<pre><code>$kgpooyaml test-pod-d4b77b989
</code></pre>
<p>如果你使用kubectl命令完成，那么你可能习惯于自动完成资源名称之类的事情。但是当你使用别名时，你还可以这样做吗？</p>
<p>这是一个重要的问题，因为如果它不起作用，那将消除这些别名的一些好处！</p>
<p>答案取决于您使用的shell。</p>
<p>对于Zsh，完成对于别名是开箱即用的。</p>
<p>不幸的是，对于Bash，默认情况下，对于别名，完成功能不起作用。好消息是它可以通过一些额外的步骤来完成。下一节将介绍如何执行此操作。</p>
<h3>在Bash中启用别名的完成</h3>
<p>Bash的问题在于它尝试在别名上尝试完成（每当你按Tab键），而不是在别名命令（如Zsh）上。由于您没有所有800个别名的完成脚本，因此不起作用。</p>
<p><a href="https://github.com/cykerway/complete-alias">complete-alias</a>项目提供了解决这个问题的通用解决方案。它使用别名的完成机制，在内部将别名扩展到别名命令，并返回扩展命令的完成建议。这意味着，它使别名的完成行为与别名命令完全相同。</p>
<p>在下文中，我将首先解释如何安装complete-alias，然后如何配置它以启用所有kubectl别名的完成。</p>
<h4>安装complete-alias</h4>
<p>首先，complete-alias依赖于<a href="https://github.com/scop/bash-completion">bash-completion</a>。因此，您需要确保在安装complete-alias之前安装了bash-completion。早先已经为Linux和macOS提供了相关说明。</p>
<blockquote>
<p>对于macOS用户的重要注意事项：与kubectl完成脚本一样，complete-alias不适用于Bash 3.2，这是macOS上Bash的默认版本。特别是，complete-alias依赖于bash-completion v2（brew install bash-completion@2），它至少需要Bash 4.1。这意味着，要在macOS上使用complete-alias，您需要安装较新版本的Bash。</p>
</blockquote>
<p>要安装complete-alias，您只需bash_completion.sh从GitHub存储库下载脚本，并将其在您的~/.bashrc文件中source：</p>
<pre><code>source ~/bash_completion.sh
</code></pre>
<p>重新加载shell后，应正确安装complete-alias。</p>
<h4>启用kubectl别名的完成</h4>
<p>从技术上讲，complete-alias提供了_complete_aliasshell函数。此函数检查别名并返回别名命令的完成建议。</p>
<p>要将其与特定别名挂钩，您必须使用completeBash内置来设置别名_complete_alias的完成功能。</p>
<p>举个例子，我们k来看一下代表kubectl命令的别名。要设置_complete_alias此别名的完成功能，您必须执行以下命令：</p>
<pre><code>$complete -F _complete_alias k
</code></pre>
<p>这样做的结果是，无论何时在k别名上自动完成，_complete_alias都会调用该函数，该函数检查别名并返回kubectl命令的完成建议。</p>
<p>作为另一个例子，让我们采用kg代表的别名kubectl get：</p>
<pre><code>$complete -F _complete_alias kg
</code></pre>
<p>同样，这样做的结果是，当您自动完成时kg，您将获得与之相同的完成建议kubectl get。</p>
<blockquote>
<p>请注意，可以以这种方式对系统上的任何别名使用complete-alias。</p>
</blockquote>
<p>因此，要启用所有 kubectl别名的完成，您只需为每个别名运行上述命令。以下代码片段完全相同（假设您安装了kubectl-aliases ~/.kubectl-aliases）：</p>
<pre><code>for _a in $(sed '/^alias /!d;s/^alias //;s/=.*$//' ~/.kubectl_aliases); do
  complete -F _complete_alias "$_a"
done
</code></pre>
<p>只需将此片段添加到您的~/.bashrc文件中，重新加载您的shell，现在您应该可以使用所有800 kubectl别名的完成！</p>
<h2>6. 使用插件扩展kubectl</h2>
<p>从<a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.12.md#sig-cli-1">版本1.12</a>开始，kubectl包含一个<a href="https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/">插件机制</a>，允许您使用自定义命令扩展kubectl。</p>
<p>以下是kubectl插件的示例，可以调用为kubectl hello：</p>
<pre><code>$ kubectl hello
Hello, I'm a kubectl plugin!
</code></pre>
<blockquote>
<p>kubectl插件机制将严格遵循Git插件机制的设计。</p>
</blockquote>
<p>本节将向您展示如何安装插件，您可以在哪里找到现有的插件，以及如何创建自己的插件。</p>
<h4>安装插件</h4>
<p>Kubectl插件作为简单的可执行文件分发，其名称的形式为kubectl-x。前缀kubectl-是必需的，接下来是允许调用插件的新kubectl子命令。</p>
<p>例如，上面显示的hello插件将作为名为的文件分发kubectl-hello。</p>
<p>要<a href="https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/#installing-kubectl-plugins">安装插件</a>，您只需将kubectl-x文件复制到您的任何目录中PATH并使其可执行（例如，使用chmod +x）。之后，您可以立即调用该插件kubectl x。</p>
<p>您可以使用以下命令列出系统上当前安装的所有插件：</p>
<pre><code>$kubectl plugin list
</code></pre>
<p>如果您有多个具有相同名称的插件，或者存在不可执行的插件文件，则此命令还会显示警告。</p>
<h3>使用krew查找和安装插件</h3>
<p>Kubectl插件可以像软件包一样共享和重用。但是在哪里可以找到其他人共享的插件？</p>
<p>该<a href="https://github.com/GoogleContainerTools/krew">krew项目</a>旨在提供一个统一的解决方案，共享，查找，安装和管理kubectl插件。该项目将自己称为“kubectl插件的包管理器”（名称krew是brew的提示）。</p>
<p>Krew 以kubectl<a href="https://github.com/GoogleContainerTools/krew-index">插件索引</a>为中心，您可以从中选择和安装。</p>
<pre><code>$ kubectl krew search | less
$ kubectl krew search view
$ kubectl krew info view-utilization
$ kubectl krew install view-utilization
$ kubectl krew list
</code></pre>
<p>如您所见，krew本身是一个kubectl插件。这意味着，安装krew本质上就像安装任何其他kubectl插件一样。您可以在<a href="https://github.com/GoogleContainerTools/krew/#installation">GitHub页面</a>上找到krew的详细安装说明。</p>
<p>最重要的krew命令如下：</p>
<pre><code># Search the krew index (with an optional search query)
$ kubectl krew search [&lt;query&gt;]
# Display information about a plugin
$ kubectl krew info &lt;plugin&gt;
# Install a plugin
$ kubectl krew install &lt;plugin&gt;
# Upgrade all plugins to the newest versions
$ kubectl krew upgrade
# List all plugins that have been installed with krew
$ kubectl krew list
# Uninstall a plugin
$ kubectl krew remove &lt;plugin&gt;
</code></pre>
<p>请注意，使用krew安装插件并不妨碍以传统方式安装插件。即使你使用krew，你仍然可以通过其他方式安装你在其他地方找到的插件（或自己创建）。</p>
<blockquote>
<p>请注意，该kubectl krew list命令仅列出已使用krew安装的插件，而该kubectl plugin list命令列出了所有插件，即使用krew安装的插件和以其他方式安装的插件。</p>
</blockquote>
<h3>在其他地方寻找插件</h3>
<p>Krew仍然是一个年轻的项目，目前<a href="https://github.com/GoogleContainerTools/krew-index/">krew索引</a>中只有大约30个插件。如果你在那里找不到你需要的东西，你可以在其他地方寻找插件，例如，在GitHub上。</p>
<p>我建议查看<a href="https://github.com/topics/kubectl-plugins">kubectl-plugins GitHub主题</a>。你会发现有几十个可用的插件值得一看。</p>
<h3>创建自己的插件</h3>
<p>当然，您可以<a href="https://kubernetes.io/docs/tasks/extend-kubectl/kubectl-plugins/#writing-kubectl-plugins">创建自己的kubectl插件</a>，这很容易实现。</p>
<p>您只需创建一个可执行文件，执行您想要的操作，为其命名kubectl-x，然后按上述方法安装它。</p>
<p>可执行文件可以是任何类型，Bash脚本，编译的Go程序，Python脚本，它确实无关紧要。唯一的要求是它可以由操作系统直接执行。</p>
<p>我们现在创建一个示例插件。在上部分中，您使用kubectl命令列出每个pod的容器镜像。您可以轻松地将此命令转换为可以调用的插件，比如说kubectl img。</p>
<p>为此，只需创建一个名为kubectl-img以下内容的文件：</p>
<pre><code>#!/bin/bash
kubectl get pods -o custom-columns='NAME:metadata.name,IMAGES:spec.containers[*].image'

</code></pre>
<p>现在使文件可执行，chmod +x kubectl-img并将其移动到您的任何PATH中的目录。之后，您可以立即开始使用该插件kubectl img！</p>
<blockquote>
<p>如上所述，kubectl插件可以用任何编程语言或脚本语言编写。如果使用shell脚本，则可以从插件轻松调用kubectl。但是，您可以使用实际编程语言编写更复杂的插件，例如，使用<a href="https://kubernetes.io/docs/reference/using-api/client-libraries/">Kubernetes客户端库</a>。如果使用<a href="https://tonybai.com/tag/go">Go</a>，您还可以使用<a href="https://github.com/kubernetes/cli-runtime">cli-runtime库</a>，它专门用于编写kubectl插件。</p>
</blockquote>
<h3>分享你的插件</h3>
<p>如果您认为其中一个插件可能对其他人有用，请随时在GitHub上分享。确保将其添加到<a href="https://github.com/topics/kubectl-plugins">kubectl-plugins主题</a>中，以便其他人可以找到它。</p>
<p>您还可以请求将您的插件添加到<a href="https://github.com/GoogleContainerTools/krew-index/">krew索引</a>中。您可以在<a href="https://github.com/GoogleContainerTools/krew/blob/master/docs/DEVELOPER_GUIDE.md">krew GitHub存储库</a>中找到有关如何执行此操作的说明。</p>
<h3>命令完成</h3>
<p>目前，插件机制遗憾的是还不支持命令完成。这意味着您需要完全键入插件名称以及插件的任何参数。</p>
<p>但是，在kubectl GitHub存储库中有一个处于open状态的<a href="https://github.com/kubernetes/kubectl/issues/585">功能请求issue</a>。因此，此功能有可能在将来的某个时间得到实现。</p>
<p>以上就是有关kubectl高效使用的所有内容了！</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/08/31/kubectl-productivity-part3/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>提高您的kubectl生产力（第二部分）：命令完成、资源规范快速查看和自定义列输出格式</title>
		<link>https://tonybai.com/2019/08/30/kubectl-productivity-part2/</link>
		<comments>https://tonybai.com/2019/08/30/kubectl-productivity-part2/#comments</comments>
		<pubDate>Fri, 30 Aug 2019 06:39:45 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[apiserver]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[bashrc]]></category>
		<category><![CDATA[bash_profile]]></category>
		<category><![CDATA[brew]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[etcd]]></category>
		<category><![CDATA[GCP]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[jsonpath]]></category>
		<category><![CDATA[k8s]]></category>
		<category><![CDATA[kube-controller-manager]]></category>
		<category><![CDATA[kube-scheduler]]></category>
		<category><![CDATA[kubectl]]></category>
		<category><![CDATA[kubelet]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[macos]]></category>
		<category><![CDATA[pod]]></category>
		<category><![CDATA[ReplicaSet]]></category>
		<category><![CDATA[RESTAPI]]></category>
		<category><![CDATA[RESTFUL]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[xpath]]></category>
		<category><![CDATA[yaml]]></category>
		<category><![CDATA[yum]]></category>
		<category><![CDATA[zsh]]></category>

		<guid isPermaLink="false">https://tonybai.com/?p=2753</guid>
		<description><![CDATA[本文翻译自《Boosting your kubectl productivity》。 第一部分：什么是kubectl？ 1. 通过命令完成(command completion)减少输入 命令完成是提高你的kubectl生产力的最有用但经常被忽视的技巧之一。 命令完成允许您使用Tab键自动完成kubectl命令的各个部分。这适用于子命令，选项和参数，包括资源名称等难以输入的内容。 在这里你可以看到kubectl命令完成的动作： 命令完成在Bash和Zsh shell下均可用。 在官方文档中包含有关设置命令完成的详细说明，下面的章节我们再带着大家回顾一下。 命令完成的工作原理 通常，命令完成是一个shell功能，它通过completion script(完成脚本)的方式工作。完成脚本是一个shell脚本，用于定义特定命令的完成行为。获取完成脚本可以完成相应的命令。 Kubectl可以使用以下命令自动生成并打印出Bash和Zsh的完成脚本： $kubectl completion bash # or $kubectl completion zsh 理论上，在适当的shell中获取此命令的输出可以完成kubectl命令。 但是，在实践中，Bash（包括Linux和macOS之间的差异）和Zsh的细节不同。以下部分解释了所有这些情况： 在Linux上为Bash设置命令完成 在macOS上设置Bash的命令完成 设置Zsh的命令完成 在Linux上的Bash Bash的完成脚本取决于bash-completion项目，因此您必须先安装它。 您可以使用各种包管理器安装bash-completion 。例如： $sudo apt-get install bash-completion # or $yum install bash-completion 您可以使用以下命令测试是否正确安装了bash-completion： $type _init_completion 如果这输出shell函数的代码，则已正确安装bash-completion。如果该命令输出not found错误，则必须将以下行添加到您的~/.bashrc文件中： $source /usr/share/bash-completion/bash_completion 是否必须将此行添加到您的~/.bashrc文件中，取决于您用于安装bash-completion的包管理器。对于APT来说，这是必要的，对于yum，则无需。 安装bash-completion后，您必须进行设置，以便在所有shell会话中获取kubectl 完成脚本。 一种方法是将以下行添加到您的~/.bashrc文件中： [...]]]></description>
			<content:encoded><![CDATA[<p>本文翻译自<a href="https://learnk8s.io/blog/kubectl-productivity/">《Boosting your kubectl productivity》</a>。</p>
<p>第一部分：<a href="https://tonybai.com/2019/08/29/kubectl-productivity-part1/">什么是kubectl？</a></p>
<h2>1. 通过命令完成(command completion)减少输入</h2>
<p>命令完成是提高你的kubectl生产力的最有用但经常被忽视的技巧之一。</p>
<p>命令完成允许您使用Tab键自动完成kubectl命令的各个部分。这适用于子命令，选项和参数，包括资源名称等难以输入的内容。</p>
<p>在这里你可以看到kubectl命令完成的动作：</p>
<p><img src="https://tonybai.com/wp-content/uploads/kubectl/kubectl-productivity-part2-1.gif" alt="img{512x368}" /></p>
<p>命令完成在<a href="https://www.gnu.org/software/bash/">Bash</a>和<a href="https://www.zsh.org/">Zsh shell</a>下均可用。</p>
<p>在<a href="https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion">官方文档</a>中包含有关设置命令完成的详细说明，下面的章节我们再带着大家回顾一下。</p>
<h3>命令完成的工作原理</h3>
<p>通常，命令完成是一个shell功能，它通过completion script(完成脚本)的方式工作。完成脚本是一个shell脚本，用于定义特定命令的完成行为。获取完成脚本可以完成相应的命令。</p>
<p>Kubectl可以使用以下命令自动生成并打印出Bash和Zsh的完成脚本：</p>
<pre><code>$kubectl completion bash
# or
$kubectl completion zsh
</code></pre>
<p>理论上，在适当的shell中获取此命令的输出可以完成kubectl命令。</p>
<p>但是，在实践中，Bash（包括Linux和macOS之间的差异）和Zsh的细节不同。以下部分解释了所有这些情况：</p>
<ul>
<li>在Linux上为Bash设置命令完成</li>
<li>在macOS上设置Bash的命令完成</li>
<li>设置Zsh的命令完成</li>
</ul>
<h3>在Linux上的Bash</h3>
<p>Bash的完成脚本取决于<a href="https://github.com/scop/bash-completion">bash-completion项目</a>，因此您必须先安装它。</p>
<p>您可以使用<a href="https://github.com/scop/bash-completion#installation">各种包管理器</a>安装bash-completion 。例如：</p>
<pre><code>$sudo apt-get install bash-completion
# or
$yum install bash-completion
</code></pre>
<p>您可以使用以下命令测试是否正确安装了bash-completion：</p>
<pre><code>$type _init_completion

</code></pre>
<p>如果这输出shell函数的代码，则已正确安装bash-completion。如果该命令输出not found错误，则必须将以下行添加到您的~/.bashrc文件中：</p>
<pre><code>$source /usr/share/bash-completion/bash_completion
</code></pre>
<blockquote>
<p>是否必须将此行添加到您的~/.bashrc文件中，取决于您用于安装bash-completion的包管理器。对于APT来说，这是必要的，对于yum，则无需。</p>
</blockquote>
<p>安装bash-completion后，您必须进行设置，以便在所有shell会话中获取kubectl 完成脚本。</p>
<p>一种方法是将以下行添加到您的~/.bashrc文件中：</p>
<pre><code>source &lt;(kubectl completion bash)
</code></pre>
<p>另一种可能性是将kubectl完成脚本添加到/etc/bash_completion.d目录中（如果它不存在则创建它）：</p>
<pre><code>$kubectl completion bash &gt;/etc/bash_completion.d/kubectl
</code></pre>
<blockquote>
<p>/etc/bash_completion.d目录中的所有完成脚本都是由bash-completion自动获取的。</p>
</blockquote>
<p>两种方法都是等价的。</p>
<p>重新加载shell后，kubectl命令完成应该正常工作！</p>
<h3>在MacOS上的Bash</h3>
<p>有了macOS，就会出现轻微的复杂情况。原因是macOS上的Bash默认版本是3.2，这已经过时了。遗憾的是，kubectl完成脚本至少需要Bash 4.1，因此不适用于Bash 3.2。</p>
<blockquote>
<p>Apple在macOS中包含过时版本的Bash的原因是较新版本使用Apple不支持的GPLv3许可证。</p>
</blockquote>
<p>这意味着，要在macOS上使用kubectl命令完成，<strong>您必须安装较新版本的Bash</strong>。您甚至可以将它设为新的默认shell，这将为您节省很多此类麻烦。这实际上并不困难，您可以在我之前编写的macOS文章中的升级Bash中找到说明。</p>
<p><strong>在继续之前，请确保您现在确实使用的是Bash 4.1或更新版本（请查看bash &#8211;version）</strong>。</p>
<p>Bash的完成脚本取决于<a href="https://github.com/scop/bash-completion">bash-completion项目</a>，因此您必须先安装它。</p>
<p>您可以使用<a href="https://brew.sh/">Homebrew</a>安装bash-completion ：</p>
<pre><code>$brew install bash-completion@2
</code></pre>
<blockquote>
<p>bash-completion v2的@2代表。kubectl完成脚本需要bash-completion v2，而bash-completion v2至少需要Bash 4.1。这就是您不能在低于4.1的Bash版本上使用kubectl完成脚本的原因。</p>
</blockquote>
<p>该brew install命令的输出包含一个“警告”部分，其中包含将以下行添加到您的~/.bash_profile文件的说明：</p>
<pre><code>export BASH_COMPLETION_COMPAT_DIR=/usr/local/etc/bash_completion.d
[[ -r "/usr/local/etc/profile.d/bash_completion.sh" ]] &amp;&amp; . "/usr/local/etc/profile.d/bash_completion.sh"
</code></pre>
<p>您必须这样做才能完成bash-completion的安装。但是，我建议将这些行添加到您~/.bashrc文件中而不是~/.bash_profile文件中。这能确保子shell中也可以使用bash-completion。</p>
<p>重新加载shell后，可以使用以下命令测试是否正确安装了bash-completion：</p>
<pre><code>$type _init_completion
</code></pre>
<p>如果这输出shell函数的代码，那么你就完成了。</p>
<p>现在，您必须进行设置以便kubectl 完成脚本在所有shell会话中获取。</p>
<p>一种方法是将以下行添加到您的~/.bashrc文件中：</p>
<pre><code>source &lt;(kubectl completion bash)
</code></pre>
<p>另一种可能性是将kubectl完成脚本添加到/usr/local/etc/bash_completion.d目录：</p>
<pre><code>$kubectl completion bash &gt;/usr/local/etc/bash_completion.d/kubectl
</code></pre>
<blockquote>
<p>这仅在您使用Homebrew安装bash-completion时才有效。在这种情况下，bash-completion会在此目录中提供所有完成脚本。</p>
</blockquote>
<p>如果您还使用Homebrew安装了kubectl，您甚至不必执行上述步骤，因为完成脚本应该已经通过kubectl howbrew formula放在/usr/local/etc/bash_completion.d目录中了。在这种情况下，kubectl完成应该在安装bash-completion后自动开始工作。</p>
<p>最后，所有这些方法都是等效的。</p>
<p>重新加载shell后，kubectl完成应该正常工作！</p>
<h3>Zsh</h3>
<p>Zsh的完成脚本没有任何依赖项。因此，您所要做的就是设置所有内容，以便在所有shell会话中获取源代码。</p>
<p>您可以通过在~/.zshrc文件中添加以下行来完成此操作：</p>
<pre><code>source &lt;(kubectl completion zsh)
</code></pre>
<p>如果在重新加载shell后出现错误:command not found: compdef，则必须启用compdef内置功能，您可以通过将以下内容添加到~/.zshrc文件的开头来执行此操作：</p>
<pre><code>autoload -Uz compinit
compinit
</code></pre>
<h2>2. 快速查找资源规范</h2>
<p>创建YAML资源定义时，您需要知道这些资源的字段及其含义。一个可以查找到此类信息的位置是在<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/">API参考文档</a>中，那里包含了所有资源的完整规范。</p>
<p>但是，每次需要查找某些内容时都要切换到Web浏览器很乏味。因此，kubectl提供了kubectl explain命令，可以打印出终端中所有资源的资源规范。</p>
<p>kubectl explain用法如下：</p>
<pre><code>$kubectl explain resource[.field]...
</code></pre>
<p>该命令输出所请求资源或字段的规范。kubectl explain显示的信息与API参考中的信息相同。</p>
<p>默认情况下，kubectl explain仅显示单个级别的字段。您可以使用显示整个字段树的标志:&#8211;recursive：</p>
<pre><code>$kubectl explain deployment.spec --recursive
</code></pre>
<p>如果您不确定可以使用哪些资源名称，可以使用kubectl explain以下命令显示所有这些名称：</p>
<pre><code>$kubectl api-resources
</code></pre>
<p>此命令以复数形式显示资源名称（例如，deployments而不是deployment）。对于拥有短名称的资源，它还显示该资源的短名称（例如：deploy）。不要担心这些差异，对于kubectl来说，所有这些名称变体都是等同的。也就是说，你可以在kubectl explain中使用它们中的任何一个。</p>
<p>例如，以下所有命令都是等效的：</p>
<pre><code>$kubectl explain deployments.spec
# or
$kubectl explain deployment.spec
# or
$kubectl explain deploy.spec
</code></pre>
<h2>3. 使用自定义列输出格式</h2>
<p>kubectl get命令的默认输出格式（用于读取资源）如下：</p>
<pre><code>$kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
engine-544b6b6467-22qr6   1/1     Running   0          78d
engine-544b6b6467-lw5t8   1/1     Running   0          78d
engine-544b6b6467-tvgmg   1/1     Running   0          78d
web-ui-6db964458-8pdw4    1/1     Running   0          78d

</code></pre>
<p>这对于人类而言，是一种很好的可读格式，但它只包含有限的信息。如您所见，每个资源只显示一些字段（与完整资源定义相比）。</p>
<p>这就是自定义列输出格式的用武之地。它允许您自由定义要显示在其中的列和数据。您可以选择要在输出中显示为单独列的资源的任何字段</p>
<p>自定义列输出选项的用法如下：</p>
<pre><code>-o custom-columns=&lt;header&gt;:&lt;jsonpath&gt;[,&lt;header&gt;:&lt;jsonpath&gt;]...
</code></pre>
<p>您必须将每个输出列定义为一</p>
<p>&lt;</p>
<p>header>:<jsonpath>对：</p>
<ul>
<li>
<p>&lt;</p>
</li>
</ul>
<p>header> 是列的名称，您可以选择任何您想要的。<br />
* <jsonpath> 是一个选择资源字段的表达式（在下面更详细地说明）。</p>
<p>我们来看一个简单的例子：</p>
<pre><code>$ kubectl get pods -o custom-columns='NAME:metadata.name'
NAME
engine-544b6b6467-22qr6
engine-544b6b6467-lw5t8
engine-544b6b6467-tvgmg
web-ui-6db964458-8pdw4
</code></pre>
<p>这里，输出包含一个显示所有Pod名称的列。</p>
<p>选择Pod名称的表达式是metadata.name。这样做的原因是Pod的名称在Pod资源字段的metadata的name字段中定义（您可以在API参考中查找或使用kubectl explain pod.metadata.name）。</p>
<p>现在，假设您要在输出中添加一个附加列，例如，显示每个Pod正在运行的节点。为此，您只需向自定义列选项添加适当的列规范：</p>
<pre><code>$kubectl get pods \
  -o custom-columns='NAME:metadata.name,NODE:spec.nodeName'
NAME                      NODE
engine-544b6b6467-22qr6   ip-10-0-80-67.ec2.internal
engine-544b6b6467-lw5t8   ip-10-0-36-80.ec2.internal
engine-544b6b6467-tvgmg   ip-10-0-118-34.ec2.internal
web-ui-6db964458-8pdw4    ip-10-0-118-34.ec2.internal
</code></pre>
<p>选择节点名称的表达式是spec.nodeName。这是因为已调度Pod的节点保存在Pod的spec.nodeName字段中（请参阅参考资料kubectl explain pod.spec.nodeName）。</p>
<blockquote>
<p>请注意，Kubernetes资源字段区分大小写。</p>
</blockquote>
<p>您可以通过这种方式将资源的任何字段设置为输出列。只需浏览资源规范并尝试使用您喜欢的任何字段！</p>
<p>但首先，让我们仔细看看这些字段选择表达式。</p>
<h3>JSONPath表达式</h3>
<p>选择资源字段的表达式基于<a href="https://goessner.net/articles/JsonPath/index.html">JSONPath</a>。</p>
<p>JSONPath是一种从JSON文档中提取数据的语言（类似于XPath for XML）。选择单个字段只是JSONPath的最基本用法。它有很多功能，如列表选择器，过滤器等。</p>
<p>但是，kubectl explain仅支持JSONPath功能的一部分。以下通过示例用法总结了这些支持的功能：</p>
<pre><code># Select all elements of a list
$kubectl get pods -o custom-columns='DATA:spec.containers[*].image'

# Select a specific element of a list
$kubectl get pods -o custom-columns='DATA:spec.containers[0].image'

# Select those elements of a list that match a filter expression
$kubectl get pods -o custom-columns='DATA:spec.containers[?(@.image!="nginx")].image'

# Select all fields under a specific location, regardless of their name
$kubectl get pods -o custom-columns='DATA:metadata.*'

# Select all fields with a specific name, regardless of their location
$kubectl get pods -o custom-columns='DATA:..image'
</code></pre>
<p>特别重要的是[]操作符。Kubernetes资源的许多字段都是列表，此运算符允许您选择这些列表中的项目。它通常与通配符一起使用，[*]以选择列表中的所有项目。</p>
<p>您将在下面找到一些使用此表示法的示例。</p>
<h3>示例应用程序</h3>
<p>使用自定义列输出格式的可能性是无穷无尽的，因为您可以在输出中显示资源的任何字段或字段组合。以下是一些示例应用程序，但您可以自己探索并找到对您有用的应用程序！</p>
<blockquote>
<p>提示：如果您经常使用其中一个命令，则可以为其创建shell别名。</p>
</blockquote>
<h4>显示Pods的容器镜像</h4>
<pre><code>$kubectl get pods \
  -o custom-columns='NAME:metadata.name,IMAGES:spec.containers[*].image'
NAME                       IMAGES
engine-544b6b6467-22qr6    rabbitmq:3.7.8-management,nginx
engine-544b6b6467-lw5t8    rabbitmq:3.7.8-management,nginx
engine-544b6b6467-tvgmg    rabbitmq:3.7.8-management,nginx
web-ui-6db964458-8pdw4     wordpress

</code></pre>
<p>此命令显示每个Pod的所有容器镜像的名称。</p>
<blockquote>
<p>请记住，Pod可能包含多个容器。在这种情况下，单个Pod的容器镜像在同一列中显示为逗号分隔列表。</p>
</blockquote>
<h4>显示节点的可用区域</h4>
<pre><code>$kubectl get nodes \
  -o custom-columns='NAME:metadata.name,ZONE:metadata.labels.failure-domain\.beta\.kubernetes\.io/zone'
NAME                          ZONE
ip-10-0-118-34.ec2.internal   us-east-1b
ip-10-0-36-80.ec2.internal    us-east-1a
ip-10-0-80-67.ec2.internal    us-east-1b
</code></pre>
<p>如果您的Kubernetes群集部署在公共云基础架构（例如AWS，Azure或GCP）上，则此命令非常有用。它显示每个节点所在的可用区域。</p>
<blockquote>
<p>可用区域是云的概念，表示地理区域内的一个可复制点。</p>
</blockquote>
<p>每个节点的可用区域通过特殊标签failure-domain.beta.kubernetes.io/zone获得。如果集群在公共云基础结构上运行，则会自动创建此标签，并将其值设置为节点的可用区域的名称。</p>
<p>标签不是Kubernetes资源规范的一部分，因此您无法在API参考中找到上述标签。但是，如果将节点输出为YAML或JSON，则可以看到它（以及所有其他标签）：</p>
<pre><code>$kubectl get nodes -o yaml
# or
$kubectl get nodes -o json
</code></pre>
<p>除了探索资源规范之外，这通常是发现有关资源的更多信息的好方法。</p>
<hr />
<p>我的网课“<a href="https://coding.imooc.com/class/284.html">Kubernetes实战：高可用集群搭建、配置、运维与应用</a>”在慕课网上线了，感谢小伙伴们学习支持！</p>
<p><a href="https://tonybai.com/">我爱发短信</a>：企业级短信平台定制开发专家 https://tonybai.com/<br />
smspush : 可部署在企业内部的定制化短信平台，三网覆盖，不惧大并发接入，可定制扩展； 短信内容你来定，不再受约束, 接口丰富，支持长短信，签名可选。</p>
<p>著名云主机服务厂商DigitalOcean发布最新的主机计划，入门级Droplet配置升级为：1 core CPU、1G内存、25G高速SSD，价格5$/月。有使用DigitalOcean需求的朋友，可以打开这个<a href="https://m.do.co/c/bff6eed92687">链接地址</a>：https://m.do.co/c/bff6eed92687 开启你的DO主机之路。</p>
<p>我的联系方式：</p>
<p>微博：https://weibo.com/bigwhite20xx<br />
微信公众号：iamtonybai<br />
博客：tonybai.com<br />
github: https://github.com/bigwhite</p>
<p>微信赞赏：<br />
<img src="https://tonybai.com/wp-content/uploads/wechat-zanshang-code-512x512.jpg" alt="img{512x368}" /></p>
<p>商务合作方式：撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。</p>
<p style='text-align:left'>&copy; 2019, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2019/08/30/kubectl-productivity-part2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hello，Termux</title>
		<link>https://tonybai.com/2017/11/09/hello-termux/</link>
		<comments>https://tonybai.com/2017/11/09/hello-termux/#comments</comments>
		<pubDate>Thu, 09 Nov 2017 13:51:58 +0000</pubDate>
		<dc:creator>bigwhite</dc:creator>
				<category><![CDATA[技术志]]></category>
		<category><![CDATA[AArch64]]></category>
		<category><![CDATA[addon]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[apk4fun]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[ARM]]></category>
		<category><![CDATA[Bash]]></category>
		<category><![CDATA[connectbot]]></category>
		<category><![CDATA[Debian]]></category>
		<category><![CDATA[F-Droid]]></category>
		<category><![CDATA[geek]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[go1.9.2]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[hacker's-keyboard]]></category>
		<category><![CDATA[id_rsa.pub]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[logitech]]></category>
		<category><![CDATA[Lua]]></category>
		<category><![CDATA[MIX2]]></category>
		<category><![CDATA[neocomplete]]></category>
		<category><![CDATA[oh-my-zsh]]></category>
		<category><![CDATA[openssh]]></category>
		<category><![CDATA[openssl]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[root]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[sources.list]]></category>
		<category><![CDATA[SSH]]></category>
		<category><![CDATA[sshd]]></category>
		<category><![CDATA[terminal]]></category>
		<category><![CDATA[terminal-emulator]]></category>
		<category><![CDATA[Terminal-Emulator-for-Android]]></category>
		<category><![CDATA[termux]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<category><![CDATA[ultisnips]]></category>
		<category><![CDATA[UTF8]]></category>
		<category><![CDATA[Vim]]></category>
		<category><![CDATA[vim-go]]></category>
		<category><![CDATA[vim8.0]]></category>
		<category><![CDATA[vt100]]></category>
		<category><![CDATA[zsh]]></category>
		<category><![CDATA[安卓]]></category>
		<category><![CDATA[极客]]></category>

		<guid isPermaLink="false">http://tonybai.com/?p=2454</guid>
		<description><![CDATA[程序员或多或少都有一颗Geek(极客)的心^0^。- Tony Bai 折腾开始。 这一切都源于前不久将手机换成了Xiaomi的MIX2。因为青睐开放的系统（相对于水果公司系统的封闭，当然Mac笔记本除外^0^），我长期使用Android平台的手机。但之前被三星Note3手机的“大屏”搞的不是很舒服，这两年一直用5寸及以下的手机，因为单手操作体验良好。MIX2的所谓“全面屏”概念又让我回归到了大屏时代。 除了大屏，现在手机“豪华”的硬件配置也让人惊叹：高通骁龙835，8核，最高主频 2.45GHz；6GB以上的LPDDR4x的双通道大内存，怪不得微软和高通都开始合作生产基于高通ARM处理器的Win10笔记本了，这配置支撑在笔记本上办公+浏览网页绰绰有余。不过对于不怎么玩游戏的我而言，这种配置仅仅用作手机日常功能有些浪费。于是有了“mobile coding”的想法和需求，至少现在是这样想的，冲动也好，伪需求也好，先实现了再说。 一、神器Termux，不仅仅是一个terminal emulator 所谓”mobile coding”不仅仅是要通过手机ssh到服务器端进行coding，还要支持在手机上搭建一个dev环境。dev环境这个需求是以往我安装的ConnectBot等ssh client端工具所无法提供的，而其他一些terminal工具，诸如Terminal Emulator for Android仅仅提供一些shell命令的支持，适合于那些喜爱使用命令行对Android机器进行管理的”administrator”们，但对dev环境的搭建支持有限的。于是神器Termux登场了。 Termux是什么？Termux首先是一个Android terminal emulator，可以像那些terminal工具一样，提供基本的shell操作命令；除此之外更为重要的是它不仅仅是一个terminal emulator。Termux提供了一套模拟的Linux环境，你可以在无需root、无需root、无需root的情况下，像在PC linux环境下一样进行各种Linux操作，包括使用apt工具进行安装包管理、定制shell、访问网络、编写源码、编译和运行程序，甚至将手机作为反向代理、负载均衡服务器或是Web服务器，又或是做一些羞羞的hack行为等。 1、安装 Termux仅支持Android 5.0及以上版本（估计现在绝大多数android机都满足这一条件）。在国内建议使用F-Droid安装Termux（先下载安装F-Droid，再在F-Droid内部搜索Termux，然后点击安装），国内的各种安装助手很少有对这个工具的支持。或是到apk4fun下载Termux的apk包（size非常小）到手机中安装(安装时需要连接着网络)。当前Termux的最新版本为0.54。 在桌面点击安装后的Termux图标，我们就启动了一个Termux应用，见下图： 2、Termux初始环境探索 Mix2手机的Android系统使用的是Android 7.1.1版本，桌面Launcher用的是MIUI 9.1稳定版，默认的shell是bash。通过Termux，我们可以查看Android 7.1.1.使用的Linux内核版本如下： $uname -a Linux localhost 4.4.21-perf-g6a9ee37d-06186-g2b2a77b #1 SMP PREEMPT Thu Oct 26 14:55:45 CST 2017 aarch64 Android 可以看出Linux内核是4.4.21，采用的CPU arch family是ARM aarch64。 我再来看一下Termux提供的常见目录结构： Home路径： $cd ~/ $pwd [...]]]></description>
			<content:encoded><![CDATA[<p><strong><em>程序员或多或少都有一颗<a href="https://en.wikipedia.org/wiki/Geek">Geek(极客)</a>的心^0^。- Tony Bai</em></strong></p>
<p>折腾开始。</p>
<p>这一切都源于前不久将手机换成了Xiaomi的<a href="https://en.wikipedia.org/wiki/Xiaomi_Mi_MIX_2">MIX2</a>。因为青睐开放的系统（相对于水果公司系统的封闭，当然Mac笔记本除外^0^），我长期使用<a href="https://en.wikipedia.org/wiki/Android_(operating_system)">Android平台</a>的手机。但之前被三星Note3手机的“大屏”搞的不是很舒服，这两年一直用5寸及以下的手机，因为单手操作体验良好。MIX2的所谓“全面屏”概念又让我回归到了大屏时代。</p>
<p>除了大屏，现在手机“豪华”的硬件配置也让人惊叹：高通骁龙835，8核，最高主频 2.45GHz；6GB以上的LPDDR4x的双通道大内存，怪不得微软和高通都开始合作生产基于高通ARM处理器的Win10笔记本了，这配置支撑在笔记本上办公+浏览网页绰绰有余。不过对于不怎么玩游戏的我而言，这种配置仅仅用作手机日常功能有些浪费。于是有了“mobile coding”的想法和需求，至少现在是这样想的，冲动也好，伪需求也好，先实现了再说。</p>
<h2>一、神器Termux，不仅仅是一个terminal emulator</h2>
<p>所谓”mobile coding”不仅仅是要通过手机ssh到服务器端进行coding，还要支持在手机上搭建一个dev环境。dev环境这个需求是以往我安装的<a href="https://github.com/connectbot/connectbot">ConnectBot</a>等ssh client端工具所无法提供的，而其他一些terminal工具，诸如<a href="https://github.com/jackpal/Android-Terminal-Emulator">Terminal Emulator for Android</a>仅仅提供<a href="https://github.com/jackpal/Android-Terminal-Emulator/wiki/Android-Shell-Command-Reference">一些shell命令</a>的支持，适合于那些喜爱使用命令行对Android机器进行管理的”administrator”们，但对dev环境的搭建支持有限的。于是神器<a href="https://termux.com/">Termux</a>登场了。</p>
<p><a href="https://github.com/termux/termux-app">Termux</a>是什么？Termux首先是一个Android terminal emulator，可以像那些terminal工具一样，提供基本的shell操作命令；除此之外更为重要的是它不仅仅是一个terminal emulator。Termux提供了一套模拟的<a href="http://tonybai.com/tag/linux">Linux</a>环境，你可以在<strong>无需root、无需root、无需root</strong>的情况下，像在PC linux环境下一样进行各种Linux操作，包括使用<a href="https://en.wikipedia.org/wiki/APT_(Debian)">apt工具</a>进行安装包管理、定制shell、访问网络、编写源码、编译和运行程序，甚至将手机作为反向代理、负载均衡服务器或是Web服务器，又或是做一些羞羞的hack行为等。</p>
<h3>1、安装</h3>
<p>Termux仅<a href="https://github.com/termux/termux-app/issues/6">支持Android 5.0及以上版本</a>（估计现在绝大多数android机都满足这一条件）。在国内建议使用<a href="https://f-droid.org/packages/com.termux/">F-Droid</a>安装Termux（先下载安装F-Droid，再在F-Droid内部搜索Termux，然后点击安装），国内的各种安装助手很少有对这个工具的支持。或是到<a href="https://www.apk4fun.com/apk/74133/">apk4fun</a>下载Termux的apk包（size非常小）到手机中安装(安装时需要连接着网络)。当前Termux的最新版本为<a href="https://github.com/termux/termux-app/releases/tag/v0.54">0.54</a>。</p>
<p>在桌面点击安装后的Termux图标，我们就启动了一个Termux应用，见下图：</p>
<p><img src="http://tonybai.com/wp-content/uploads/termux-initial-start.jpg" alt="img{512x368}" /></p>
<h3>2、Termux初始环境探索</h3>
<p>Mix2手机的Android系统使用的是<a href="https://www.android.com/phones/">Android 7.1.1版本</a>，桌面Launcher用的是<a href="https://en.wikipedia.org/wiki/MIUI">MIUI 9.1</a>稳定版，默认的shell是<a href="http://tonybai.com/2009/02/27/make-bash-my-default-shell/">bash</a>。通过Termux，我们可以查看Android 7.1.1.使用的<a href="http://tonybai.com/2012/03/15/linux-kernel-hacking-series-kernel-config-compile-and-install/">Linux内核</a>版本如下：</p>
<pre><code>$uname -a
Linux localhost 4.4.21-perf-g6a9ee37d-06186-g2b2a77b #1 SMP PREEMPT Thu Oct 26 14:55:45 CST 2017 aarch64 Android
</code></pre>
<p>可以看出<a href="http://tonybai.com/2012/03/15/linux-kernel-hacking-series-kernel-config-compile-and-install/">Linux内核</a>是4.4.21，采用的CPU arch family是<a href="https://en.wikipedia.org/wiki/ARM_architecture">ARM</a> <a href="https://en.wikipedia.org/wiki/ARM_architecture#AArch64_features">aarch64</a>。</p>
<p>我再来看一下Termux提供的常见目录结构：</p>
<p>Home路径：</p>
<pre><code>$cd ~/
$pwd
/data/data/com.termux/files/home

//或者通过环境变量HOME获取：

$echo $HOME
/data/data/com.termux/files/home
</code></pre>
<p>长期使用Linux的朋友可能会发现，这个HOME路径好是奇怪，一般的标准<a href="https://en.wikipedia.org/wiki/Linux_distribution">Linux发行版</a>，比如<a href="http://tonybai.com/tag/ubuntu">Ubuntu</a>都是在”/home”下放置用户目录，但termux环境中HOME路径却是一个<strong>奇怪的位置</strong>。在<a href="https://wiki.termux.com/wiki/Main_Page">Termux官方Wiki</a>中，我们得到的答案是：Termux是一个prefixed system。</p>
<blockquote>
<p>这个prefix的含义我理解颇有些类似于我们在使用configure脚本时指定的&#8211;prefix参数的含义。我们在执行configure脚本时，如果不显式地给&#8211;prefix传入值，那么make install后，包将被install在<strong>标准位置</strong>；否则将被install在&#8211;prefix值所指定的位置。</p>
</blockquote>
<p>prefixed system意味着Termux中所有binaries、libraries、configs都不是放在标准的位置，比如：/usr/bin、/bin、/usr/lib、/etc等下面。Termux expose了一个特殊的环境变量:PREFIX（类似于configure &#8211;prefix参数选项)：</p>
<pre><code>$echo $PREFIX
/data/data/com.termux/files/usr

$cd $PREFIX
$ls -F
bin/  etc/  include/  lib/  libexec/  share/  tmp/  var/
</code></pre>
<p>是不是有些似曾相识？但Termux的$PREFIX路径与标准linux的根路径下的目录结构毕竟还<a href="https://wiki.termux.com/wiki/Differences_from_Linux">存在差别</a>，但有着对应关系，这种对应关系大致是：</p>
<pre><code>Termux的$PREFIX/bin  &lt;=&gt;  标准Linux环境的 /bin和/usr/bin
Termux的$PREFIX/lib  &lt;=&gt;  标准Linux环境的 /lib和/usr/lib
Termux的$PREFIX/var  &lt;=&gt;  标准Linux环境的 /var
Termux的$PREFIX/etc  &lt;=&gt;  标准Linux环境的 /etc
</code></pre>
<p>因此，基本可以认为Termux的$PREFIX/就对应于标准Linux的/路径。</p>
<h3>3、更新源和包管理</h3>
<p>Termux的牛逼之处在于它基于debian的<a href="https://en.wikipedia.org/wiki/APT_(Debian)">APT包</a>管理工具进行软件包的安装、管理和卸载，就像我们在Ubuntu下所做的那样，非常方便。</p>
<p>Termux自己<a href="http://termux.net/">维护了一个源</a>，提供各种专门为termux定制的包：</p>
<pre><code># The main termux repository:
#deb [arch=all,aarch64] http://termux.net stable main
</code></pre>
<p>同时，<a href="https://github.com/termux/termux-packages">termux-packages项目</a>为开发者和爱好者提供了构建工具和脚本，通过这些工具和脚本，我们可以将自己需要的软件包编译为可以在termux运行的版本，并补充到Termux的源之中。我大致测试了一下官方这个源还是可用的，虽然初始连接的响应很缓慢。</p>
<p>国内清华大学维护了一个<a href="https://mirror.tuna.tsinghua.edu.cn/help/termux/">Termux的镜像源</a>，你可以通过编辑 /data/data/com.termux/files/usr/etc/apt/sources.list文件或执行apt edit-sources命令编辑源(在Shell配置中添加export EDITOR=vi后，apt edit-sources才能启动编辑器进行编辑)：</p>
<pre><code># The main termux repository:
#deb [arch=all,aarch64] http://termux.net stable main
deb [arch=all,aarch64] http://mirrors.tuna.tsinghua.edu.cn/termux stable main
</code></pre>
<p>剩下的操作与Ubuntu上的一模一样，无非apt update后，利用apt install安装你想要的包。目前Termux源中都有哪些包呢？可以通过apt list命令查看：</p>
<pre><code>$apt list
Listing... Done
aapt/stable 7.1.2.33-1 aarch64
abduco/stable 0.6 aarch64
abook/stable 0.6.0pre2-1 aarch64
ack-grep/stable 2.18 all
alpine/stable 2.21 aarch64
angband/stable 4.1.0 aarch64
apache2/stable 2.4.29 aarch64
apache2-dev/stable 2.4.29 aarch64
apksigner/stable 0.4 all
apr/stable 1.6.3 aarch64
apr-dev/stable 1.6.3 aarch64
apr-util/stable 1.6.1 aarch64
apr-util-dev/stable 1.6.1 aarch64
apt/stable,now 1.2.12-3 aarch64 [installed]
apt-transport-https/stable 1.2.12-3 aarch64
... ...
zile/stable 2.4.14 aarch64
zip/stable 3.0-1 aarch64
zsh/stable,now 5.4.2-1 aarch64 [installed]
</code></pre>
<p>查看是否有需要更新的包列表：</p>
<pre><code>$apt list --upgradable
</code></pre>
<p>以安装<a href="http://tonybai.com/tag/go">golang</a>为例：</p>
<pre><code>$apt install golang
....
$go version
go version go1.9.2 android/arm64
</code></pre>
<p><img src="http://tonybai.com/wp-content/uploads/termux-apt-install-go.jpg" alt="img{512x368}" /></p>
<p>Termux源中的包似乎更新的很勤奋，<a href="http://tonybai.com/2017/07/14/some-changes-in-go-1-9/">Go 1.9.2</a>才发布没多久，这里已经是最新版本了，这点值得赞一个！</p>
<h2>二、开发环境搭建</h2>
<p>我的目标是<strong>mobile coding</strong>，需要在Termux上搭建一个dev环境，以<a href="http://tonybai.com/tag/go">Go</a>环境为例。</p>
<h3>1、sshd</h3>
<p>在搭建和配置阶段，如果直接通过Android上的软键盘操作，即便屏再大，那个体验也是较差的。我们最好通过PC连到termux上去安装和配置，这就需要我们在Termux上搭建一个<a href="https://wiki.termux.com/wiki/SSH">sshd server</a>。下面是步骤：</p>
<pre><code>$apt install openssh
$sshd
</code></pre>
<p>就这么简单，一个sshd的server就在termux的后台启动起来了。由于Termux没有root权限，无法listen数值小于1024的端口，因此termux上sshd默认的listen端口是8022。另外termux上的sshd server不支持用户名+密码的方式进行登录，只能用免密登录的方式，即将PC上的~/.ssh/id_rsa.pub写入termux上的~/.ssh/authorized_keys文件中。关于免密登录的证书生成方法和导入方式，网上资料已经汗牛充栋，这里就不赘述了。导入PC端的id_rsa.pub后，PC就可以通过下面命令登录termux了：</p>
<pre><code>$ssh 10.88.46.79  -p 8022
Welcome to Termux!

Wiki:            https://wiki.termux.com
Community forum: https://termux.com/community
IRC channel:     #termux on freenode
Gitter chat:     https://gitter.im/termux/termux
Mailing list:    termux+subscribe@groups.io

Search packages:   pkg search &lt;query&gt;
Install a package: pkg install &lt;package&gt;
Upgrade packages:  pkg upgrade
Learn more:        pkg help
</code></pre>
<p>其中10.88.46.79是手机的wlan0网卡的IP地址，可以在termux中使用ip addr命令获得:</p>
<pre><code>$ip addr show wlan0
34: wlan0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP group default qlen 3000
    ... ...
    inet 10.88.46.79/20 brd 10.88.47.255 scope global wlan0
       valid_lft forever preferred_lft forever
    ... ...
</code></pre>
<h3>2、定制shell</h3>
<p>Termux支持多种<a href="https://wiki.termux.com/wiki/Shells">主流Shell</a>，默认的Shell是<a href="http://tonybai.com/tag/bash">Bash</a>。很多开发者喜欢<a href="https://www.zsh.org/">zsh</a> + <a href="https://github.com/robbyrussell/oh-my-zsh">oh-my-zsh</a>的组合，Termux也是支持的，安装起来也是非常简单的：</p>
<pre><code>$ apt install git
$ apt install zsh
$ git clone git://github.com/robbyrussell/oh-my-zsh.git ~/.oh-my-zsh
$ cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc
$ chsh zsh
</code></pre>
<p>与在PC上安装和配置zsh和oh-my-zsh没什么两样，你完全可以按照你在PC上的风格定制zsh的Theme等，我用的就是默认theme，所以也无需做太多变化，顶多定制一下PROMPT(~/.oh-my-zsh/themes/robbyrussell.zsh-theme中的PROMPT变量)的格式^0^。</p>
<h3>3、安装vim-go</h3>
<p>在terminal内进行Go开发，<a href="https://github.com/fatih/vim-go">vim-go</a>是必备之神器。vim-go以及相关自动补齐、snippet插件安装在不同平台上都是大同小异的，之前写过两篇《<a href="http://tonybai.com/2014/11/07/golang-development-environment-for-vim">Golang开发环境搭建-Vim篇</a>》和《<a href="http://tonybai.com/2016/09/08/upgrade-vim-go/">vim-go更新小记</a>》，大家可以参考。</p>
<p>不过这里有一个较为关键的问题，那就是Termux官方源中的vim 8.0缺少了对python和lua的支持：</p>
<pre><code> $vim --version|grep py
+cryptv          +linebreak       -python          +viminfo
+cscope          +lispindent      -python3         +vreplace
$vim --version|grep lua
+dialog_con      -lua             +rightleft       +windows
</code></pre>
<p>而一些插件又恰需要这些内置的支持，比如<a href="https://github.com/SirVer/ultisnips/issues/707">ultisnips</a>需要vim自带py支持；<a href="https://github.com/Shougo/neocomplete.vim">neocomplete</a>又依赖vim的lua支持。这样如果你还想要补齐和snippet特性，你就需要在Termux下面自己编译Vim的源码了（configure时加上对python和lua的支持）。</p>
<h3>4、中文支持</h3>
<p>无论是PC还是Termux使用的都是UTF8的内码格式，但是在安装完vim-go后，我试着用vim编辑一些简单的源码，发现在vim中输入的中文都是乱码。这里通过一个配置解决了该问题：</p>
<pre><code>//~/.vimrc

添加一行：

set enc=utf8

</code></pre>
<p>至于其中的原理，可以参见我N年前写的《<a href="http://tonybai.com/2009/09/28/also-talk-about-vim-charset-configuration/">也谈VIM字符集编码设置</a>》一文。</p>
<h2>三、键盘适配</h2>
<p>现阶段，写代码还是需要键盘输入的（憧憬未来^0^）。</p>
<h3>1、软键盘</h3>
<p>使用原生自带的默认软键盘在terminal中用vim进行coding，那得多执着啊，尤其是在vim大量使用ESC键的情况下（我都没找到原生键盘中ESC键在哪里:(）。不过Termux倒是很具包容心，为原生软键盘提供了扩展支持：用两个上下音量键协助你输入一些原生键盘上没有或者难于输入的符号，比如（全部的模拟按键列表参见<a href="https://wiki.termux.com/wiki/Touch_Keyboard">这里</a>）：</p>
<pre><code>清理屏幕：用volume down + L 来模拟 ctrl + L
结束前台程序：用volume down + C 来模拟 ctrl + C
ESC：用volume up + E 来模拟
F1-F9: 用volume up + 1 ~ 9 来模拟

</code></pre>
<blockquote>
<p>据网友提示：volume up + Q键可以打开扩展键盘键，包括ESC、CTRL、ALT等，感谢。</p>
</blockquote>
<p>这样仅能满足临时的需要，要想更有效率的输入，我们需要<a href="https://github.com/klausw/hackerskeyboard">Hacker&#8217;s Keyboard</a>。顾名思义，Hacker&#8217;s Keyboard可以理解为专为Coding(无论出于何种目的)的人准备的。和Termux一样，你可以从<a href="https://f-droid.org/packages/org.pocketworkstation.pckeyboard/">F-droid</a>安装该工具。启动该app后，app界面上有明确的使用说明，如果依旧不明确，还可以查看这篇图文并茂的文章：《<a href="https://www.wikihow.com/Use-Hacker%27s-Keyboard">How to Use Hacker&#8217;s Keyboard</a>》。默认情况下，横屏时Hacker&#8217;s keyboard会使用”Full 5-row layout”，即全键盘，竖屏时，则是4-row layout。你可以通过“系统设置”中的“语言和输入法”配置中对其进行设置，让Hacker&#8217;s keyboard无论在横屏还是竖屏都采用全键盘（我们屏幕够大^0^）：</p>
<p><img src="http://tonybai.com/wp-content/uploads/termux-hackers-keyboard-landscape.jpg" alt="img{512x368}" /><br />
横屏</p>
<p><img src="http://tonybai.com/wp-content/uploads/termux-hackers-keyboard-portrait.jpg" alt="img{512x368}" /><br />
竖屏</p>
<p>Hacker&#8217;s Keyboard无法支持中文输入，这点是目前的缺憾，不过我个人写代码时绝少使用中文，该问题忽略不计。</p>
<h3>2、外接蓝牙键盘</h3>
<p>Hacker&#8217;s Keyboard虽然一定程度提升了Coding时的输入效率，但也仅是权宜之计，长时间大规模通过软键盘输入依旧不甚可取，外接键盘是必须的。对于手机而言，目前最好的外接连接方式就是蓝牙。蓝牙键盘市面上现在有很多种，我选择了老牌大厂<a href="https://en.wikipedia.org/wiki/Logitech">logitech</a>的<a href="https://www.logitech.com/en-us/product/multi-device-keyboard-k480">K480</a>。这款键盘缺点是便携性差点、按键有些硬，但按键大小适中；而那些超便携的蓝牙键盘普遍键帽太小，长时间Coding的体验是个问题。</p>
<p><img src="http://tonybai.com/wp-content/uploads/termux-mix2-logitech-k480.jpg" alt="img{512x368}" /></p>
<p>Termux对外接键盘的支持也是很好的，除了常规输入，通过键盘组合键Ctrl+Alt与其他字母的组合<a href="https://wiki.termux.com/wiki/Hardware_Keyboard">实现各种控制功能</a>，比如：</p>
<pre><code>ctrl + alt + c =&gt; 实现创建一个新的session；
ctrl + alt + 上箭头/下箭头 =&gt; 实现切换到上一个/下一个session的窗口；
ctrl + alt + f =&gt; 全屏
ctrl + alt +v =&gt; 粘贴
ctrl + alt + +/- =&gt; 实现窗口字体的放大/缩小

</code></pre>
<p>不过，外接键盘和Hacker&#8217;s keyboard有一个相同的问题，那就是针对Termux无法输入中文。我尝试了百度、搜狗等输入法，无论如何切换（正常在其他应用中，通过【shift + 空格】实现中英文切换）均只是输入英文。</p>
<h2>四、存储</h2>
<p>到目前为止，我们提到的路径都在termux的私有的内部存储(private internal storage)路径下，这类存储的特点是termux应用内部的、私有的，一旦termux被卸载，这些数据也将不复存在。Android下还有另外两种存储类型：shared internal storage和external storage。所谓shared internal storage是手机上所有App可以共享的存储空间，放在这个空间内的数据不会因为App被卸载掉而被删除掉；而外部存储(external storage)主要是指外部插入的SD Card的存储空间。</p>
<p>默认情况下，Termux只支持private internal storage，意味着你要做好数据备份，否则一旦误卸载termux，数据可就都丢失了;数据可以用git进行管理，并sync到云端。</p>
<p>Termux提供了一个名为<a href="https://github.com/termux/termux-packages/blob/master/packages/termux-tools/termux-setup-storage">termux-setup-storage</a>的工具，可以让你在Termux下访问和使用shared internal storage和external storage；该工具是<a href="https://github.com/termux/termux-packages/tree/master/packages/termux-tools">termux-tools</a>的一部分，你可以通过apt install termux-tools来安装这些工具。</p>
<p>执行termux-setup-storage(注意：这个命令只能在手机上执行才能弹出授权对话框，通过远程ssh登录后执行没有任何效果)时，手机会弹出一个对话框，让你确认授权：</p>
<p><img src="http://tonybai.com/wp-content/uploads/termux-setup-storage.jpg" alt="img{512x368}" /></p>
<p>一旦授权，termux-setup-storage就会在HOME目录下建立一个storage目录，该目录下的结构如下：</p>
<pre><code>➜  /data/data/com.termux/files/home $tree storage
storage
├── dcim -&gt; /storage/emulated/0/DCIM
├── downloads -&gt; /storage/emulated/0/Download
├── movies -&gt; /storage/emulated/0/Movies
├── music -&gt; /storage/emulated/0/Music
├── pictures -&gt; /storage/emulated/0/Pictures
└── shared -&gt; /storage/emulated/0

6 directories, 0 files
</code></pre>
<p>我们看到在我的termux下，termux-setup-storage在storage下建立了6个符号链接，其中shared指向shared internal storage的根目录，即/storage/emulated/0；其余几个分别指向shared下的若干功能目录，比如：相册、音乐、电影、下载等。我的手机没有插SD卡，可能也不支持（市面上大多数手机都已经不支持了），如果插了一张SD卡，那么termux-setup-storage还会在storage目录下j建立一个符号链接指向在external storage上的一个termux private folder。</p>
<p>现在你就可以把数据放在shared internal storage和external storage上了，当然你也可以在Termux下自由访问shared internal storage上的数据了。</p>
<h2>五、小结</h2>
<p>Termux还设计了支持扩展的Addon机制，支持通过各种Addon来丰富Termux功能，提升其能力，这些算是高级功能，在这篇入门文章里就先不提及了。好了，接下来我就可以开始我的mobile coding了，充分利用碎片时间。后续在使用Termux+k480的过程中如果遇到什么具体的问题，我再来做针对性的解析。</p>
<hr />
<p>微博：<a href="http://weibo.com/bigwhite20xx">@tonybai_cn</a><br />
微信公众号：iamtonybai<br />
github.com: https://github.com/bigwhite</p>
<p style='text-align:left'>&copy; 2017, <a href='https://tonybai.com'>bigwhite</a>. 版权所有. </p>
]]></content:encoded>
			<wfw:commentRss>https://tonybai.com/2017/11/09/hello-termux/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
	</channel>
</rss>
