零基础复现Claude Code五终端篇——赋予执行命令的超能力开篇从能改到能验证第4篇的成就我们给Agent装上了双手——它能真正读写文件了不再是纸上谈兵。但现在有个问题Agent改完代码后它不知道改对了没有。回到实习生比喻现在的Agent就像一个只会埋头干活的实习生。你让他修Bug他改完代码后说“我改好了。”你问“你测试过吗”他愣住了“呃…我不会跑测试我只会改代码…”这一篇我们要给实习生一个终端——让他能自己跑测试、查日志、看文件列表验证自己的工作成果。这一篇是能力的关键跃升——从盲改到验证式修改。本节目标读完这篇文章你将理解命令执行的风险与边界为什么这是Agent最危险的能力实现安全的终端工具用白名单黑名单双重过滤掌握输出截断策略避免命令输出撑爆Token预算看到Agent的完整工作流读文件→改文件→跑测试→看结果→调整原理深潜命令执行的双刃剑 回到第一篇的公式还记得我们在第一篇建立的公式吗循环 t 0, 1, 2, ... Thought_t, Action_t LLM(S_t) Observation_t Execute(Action_t) ← 本篇增加run_cmd工具 S_{t1} S_t (Thought_t, Action_t, Observation_t)第4篇我们实现了read_file和write_fileAgent能读写文件了。这一篇我们要实现run_cmd让Agent能执行命令验证自己的修改。为什么命令执行是最危险的能力对比三种工具的风险等级工具风险等级最坏情况可逆性read_file 低读取敏感文件完全可逆只读write_file 中误改重要文件部分可逆有备份run_cmd 高删除整个项目不可逆为什么run_cmd最危险# read_file 最多泄露信息read_file(.env)# 读到密码但文件还在# write_file 可以备份恢复write_file(main.py,bug)# 改坏了但有.backup# run_cmd 可能造成灾难run_cmd(rm -rf /)# 整个系统被删除无法恢复所以我们必须极其谨慎地设计这个工具。subprocess.run 的三个关键参数Python的subprocess.run是执行命令的标准方式importsubprocess resultsubprocess.run([ls,-la],# 命令和参数列表形式capture_outputTrue,# 捕获stdout和stderrtextTrue,# 返回字符串而不是bytestimeout30# 30秒超时防止卡死)print(result.stdout)# 命令的标准输出print(result.stderr)# 命令的错误输出print(result.returncode)# 退出码0成功关键洞察capture_outputTrue让我们能拿到命令输出反馈给模型textTrue直接得到字符串不需要decodetimeout防止命令卡死如cat /dev/random输出截断策略假设Agent执行了cat large_file.log文件有10MB。如果全部返回给模型10MB文本 ≈ 250万Token ≈ $50按GPT-4价格而且会超出模型的上下文窗口。解决方案截断输出MAX_OUTPUT_LENGTH2000# 约400个Tokeniflen(output)MAX_OUTPUT_LENGTH:returnf{output[:MAX_OUTPUT_LENGTH]}\n\n... (输出太长已截断共{len(output)}字符)else:returnoutput公式返回内容 前N字符 截断提示 if len N else 全部动手实操实现安全的终端工具现在我们开始写代码。目标是实现一个安全的run_cmd工具。第一步定义安全策略在tools.py中添加安全配置importsubprocessimportshlex# 危险命令黑名单绝对禁止DANGEROUS_COMMANDS[rm,rmdir,del,# 删除format,mkfs,# 格式化dd,# 磁盘操作,,|,# 重定向可能覆盖文件sudo,su,# 提权chmod,chown,# 权限修改curl,wget,# 网络请求可能下载恶意代码]# 安全命令白名单只允许这些SAFE_COMMANDS[ls,dir,pwd,cd,# 目录操作cat,head,tail,less,# 文件查看grep,find,wc,# 搜索统计git,# Git操作只读python,node,npm,# 运行脚本pytest,jest,cargo,# 测试命令]MAX_OUTPUT_LENGTH2000# 输出长度限制第二步实现run_cmd工具核心逻辑defrun_cmd(command:str)-str: 执行Shell命令只读操作 参数 command: 命令字符串如 ls -la 返回 命令输出或错误信息 try:# 安全检查1黑名单过滤fordangerousinDANGEROUS_COMMANDS:ifdangerousincommand.lower():returnf⛔ 拒绝执行命令包含危险操作 {dangerous}# 安全检查2白名单验证cmd_partsshlex.split(command)ifnotcmd_parts:return错误空命令base_cmdcmd_parts[0]ifbase_cmdnotinSAFE_COMMANDS:returnf⛔ 拒绝执行{base_cmd} 不在安全命令列表中# 执行命令resultsubprocess.run(cmd_parts,capture_outputTrue,textTrue,timeout30,# 30秒超时cwdos.getcwd())# 合并stdout和stderroutputresult.stdoutifresult.stderr:outputf\n[stderr]:{result.stderr}# 截断过长输出iflen(output)MAX_OUTPUT_LENGTH:returnf{output[:MAX_OUTPUT_LENGTH]}\n\n... (输出已截断共{len(output)}字符)returnoutputifoutputelse(命令执行成功无输出)exceptsubprocess.TimeoutExpired:return错误命令执行超时30秒exceptExceptionase:returnf错误{str(e)}代码解读约50行符合≤80行要求双重安全检查黑名单白名单用shlex.split安全解析命令防止注入30秒超时防止卡死输出截断防止Token爆炸第三步集成到工具分发器在tools.py的execute_tool函数中添加defexecute_tool(action:str)-str:tool_name,argsparse_action(action)iftool_nameisNone:returnf错误无法解析Action -{action}iftool_nameread_file:# ... 已有代码eliftool_namewrite_file:# ... 已有代码eliftool_namerun_cmd:iflen(args)!1:return错误run_cmd需要1个参数命令字符串returnrun_cmd(args[0])else:returnf错误未知工具 -{tool_name}第四步更新Agent的System Prompt在react_agent.py中更新System Promptself.system_prompt你是一个Python工程师Agent。 可用工具 - read_file(path): 读取文件内容 - write_file(path, content): 写入文件 - run_cmd(command): 执行Shell命令只读操作如ls、cat、pytest ⚠️ 命令执行限制 - 只能执行安全的只读命令ls、cat、grep、pytest等 - 禁止删除、修改权限、网络请求等危险操作 - 命令输出会被截断到2000字符 输出格式 Thought: [你的思考] Action: [工具调用] 示例 Thought: 我修改了代码应该运行测试验证 Action: run_cmd(pytest test_main.py) 第五步测试完整工作流创建测试文件test_full_workflow.pyfromreact_agentimportReActAgentimportos# 创建测试环境os.makedirs(test_workspace,exist_okTrue)# 创建一个有Bug的测试文件withopen(test_workspace/calculator.py,w)asf:f.write(def add(a, b): return a - b # Bug: 应该是加法 def test_add(): assert add(2, 3) 5 )# 创建AgentagentReActAgent(max_iterations15)# 任务让Agent修复Bug并验证resultagent.run( 请帮我修复test_workspace/calculator.py中的Bug。 步骤 1. 读取文件找出Bug 2. 修复Bug 3. 运行pytest验证修复是否成功 )print(\n*60)print(f最终结果{result})运行后你会看到Agent的完整工作流[第 1 轮] Thought: 我需要先读取文件看看代码有什么问题 Action: read_file(test_workspace/calculator.py) Observation: def add(a, b): return a - b # Bug: 应该是加法 ... [第 2 轮] Thought: 我看到了Bugadd函数应该返回ab但写成了a-b Action: write_file(test_workspace/calculator.py, ...) Observation: 成功文件已保存 [第 3 轮] Thought: 我应该运行pytest验证修复是否正确 Action: run_cmd(pytest test_workspace/calculator.py) Observation: test session starts collected 1 item test_workspace/calculator.py . [100%] 1 passed in 0.01s [第 4 轮] Thought: 测试通过了Bug已成功修复 ✅ Answer: Bug已修复。问题是add函数写成了减法a-b已改为加法abpytest测试通过。恭喜你的Agent现在能完成改代码→跑测试→验证的完整闭环了 ⚠️ 安全警告必读⚠️命令执行是Agent最危险的能力必须极其谨慎。强烈建议的安全措施在隔离环境中测试用Docker容器docker run -it python:3.9 bash用虚拟机VirtualBox、VMware用临时目录专门的test_workspace永远不要在生产环境运行❌ 不要在服务器上运行Agent❌ 不要在包含重要数据的目录运行❌ 不要给Agent sudo权限定期审查白名单即使是安全命令也可能被滥用例如python -c import os; os.system(rm -rf /)考虑禁用python -c、node -e等监控Agent的行为记录所有执行的命令设置异常检测如频繁失败人工审核高风险操作与真实代码的对照在真实的Claude Code实现中rust版本这部分对应的是我们的实现真实代码位置关键差异run_cmd()crates/runtime/src/bash.rs的execute_bash()真实版支持沙箱模式、流式输出黑名单/白名单crates/runtime/src/permissions.rs真实版用更复杂的权限系统输出截断execute_bash()内部逻辑真实版支持分页、智能截断想深入研究的读者打开crates/runtime/src/bash.rs搜索execute_bash你会看到完整的命令执行逻辑打开crates/runtime/src/sandbox.rs可以看到沙箱隔离的实现完整代码本篇展示的是核心逻辑约70行完整实现包括更多安全检查、日志记录请查看GitHub仓库。命令执行的3个设计原则通过上面的实现我们总结出设计命令执行工具的3个原则原则1默认拒绝显式允许❌ 不安全的设计# 允许所有命令只禁止几个危险的ifcmdnotin[rm,sudo]:execute(cmd)✅ 安全的设计# 只允许白名单中的命令ifcmdinSAFE_COMMANDS:execute(cmd)为什么你无法列举所有危险命令但可以列举所有安全命令。原则2多层防御❌ 单一防御# 只检查命令名ifcmdls:execute(cmd)✅ 多层防御# 1. 黑名单检查# 2. 白名单检查# 3. 参数检查# 4. 超时限制# 5. 输出截断为什么一层防御可能被绕过多层防御更安全。原则3失败时安全❌ 不安全的失败try:execute(cmd)except:pass# 静默失败Agent不知道出错了✅ 安全的失败try:execute(cmd)exceptExceptionase:returnf错误{str(e)}# 返回错误信息Agent能调整策略为什么Agent需要知道失败原因才能调整策略。 自检清单读完本篇请确认在进入下一篇之前请确认你能回答以下问题为什么run_cmd比read_file和write_file更危险黑名单和白名单的区别是什么哪个更安全为什么需要截断命令输出subprocess.run的timeout参数有什么作用你能说出3个应该禁止的危险命令吗如果都能回答恭喜你Agent的终端部分你已经掌握了。下一篇见⚠️ 新手容易踩的坑坑1用shellTrue执行命令危险subprocess.run(cmd, shellTrue)容易被注入安全subprocess.run(shlex.split(cmd))用列表形式坑2忘记设置timeout后果cat /dev/random会让程序卡死正确做法始终设置timeout30坑3白名单太宽松错误允许python命令问题python -c import os; os.system(rm -rf /)正确做法只允许pytest等特定测试命令坑4没有截断输出后果ls -R /输出几MBToken预算瞬间耗尽正确做法限制输出长度到2000字符下一步把所有拼图组装起来现在你已经学会了实现安全的命令执行工具设计黑名单白名单双重过滤处理命令超时和输出截断看到Agent的完整工作流读→写→测试→验证但有一个关键问题还没解决我们的Agent还是散装的——llm_client.py、tools.py、react_agent.py三个文件没有统一的入口。下一篇我们将组装完整的Mini Claude Code统一的命令行入口python agent.py 帮我修Bug完整的错误处理和日志配置文件支持设置模型、Token限制等一个可以真正使用的Agent系统这就是从教学原型到可用工具的关键一步。预告一个核心问题如何让Agent在遇到错误时自动重试而不是直接放弃答案在下一篇揭晓。系列进度✅ 第1篇总览与前置准备——Claude Code到底是什么✅ 第2篇地基篇——让模型开口说话System Prompt的艺术✅ 第3篇灵魂篇——ReAct循环的骨架✅ 第4篇双手篇——赋予读写文件的能力✅ 第5篇终端篇——赋予执行命令的超能力⏭️ 第6篇整合篇——组装Mini Claude Code第7篇上下文篇——让Agent看懂整个文件夹第8篇反思与展望——我们得到了什么还缺什么