主题
8.5 终端执行与代码编辑:受控 Shell 与 str_replace 行级替换
“修补一件衣服上的纽扣,绝不需要把整件衣服拆成碎布重新织一遍;外科医生做微创手术,靠的是毫米级的精准手术刀!”
⚙️ 赋予手脚:Coding Agent 的两大核心工具
一个真正的 Coding Agent(如 Claude Code、Cursor、Devin)与聊天机器人最根本的区别,在于它能直接操作操作系统:
- 终端执行器 (
run_bash):安装依赖、运行测试、查看进程、执行 Git 命令; - 代码编辑器 (
str_replace):读写文件、修改代码、精准重构。
🚫 为什么严禁让大模型全量重写文件?
早期 Agent 常常使用 write_file(path, full_content)。当面对 300 行以上的代码文件时,全量重写会带来三大灾难性后果:
- Token 消耗暴增:哪怕只改一行变量名,也要消耗几千 Token 重写全文件;
- 长文本生成截断:一旦触发
max_tokens上限,文件末尾代码直接丢失; - “幻觉吞代码”:大模型极易偷懒输出
// ... 其余逻辑保持不变 ...,直接把你的核心业务逻辑给清空覆盖了!
因此,顶尖编程工具 Claude Code 的看家本领就是基于精准子字符串替换的 str_replace!
💻 核心算法实现:str_replace 唯一性与 Diff 校验
在 code/s05_terminal_and_edit.py 中,我们实现了唯一性替换,并在读写前用 realpath + commonpath 检查路径:目标文件即使通过 .. 或符号链接绕路,也不能逃出当前工作区。
python
def str_replace(file_path: str, old_str: str, new_str: str) -> Tuple[bool, str, str]:
if not os.path.exists(file_path):
return False, f"❌ 文件 [{file_path}] 不存在", ""
with open(file_path, "r", encoding="utf-8") as f:
original_content = f.read()
# 1. 严格校验唯一性:old_str 必须在文件中恰好出现 1 次
occurrences = original_content.count(old_str)
if occurrences == 0:
return False, "❌ 替换失败:文件中未找到目标文本,可能是上下文不匹配", ""
if occurrences > 1:
return False, f"❌ 替换失败:目标文本出现了 {occurrences} 次!请多提供 1-2 行上下文以唯一定位", ""
# 2. 精准局部替换
modified_content = original_content.replace(old_str, new_str, 1)
# 3. 自动生成标准 Unified Diff
diff_lines = list(difflib.unified_diff(
original_content.splitlines(keepends=True),
modified_content.splitlines(keepends=True),
fromfile=f"a/{os.path.basename(file_path)}",
tofile=f"b/{os.path.basename(file_path)}",
n=3
))
diff_str = "".join(diff_lines)
# 4. 原子存盘写回
with open(file_path, "w", encoding="utf-8") as f:
f.write(modified_content)
return True, "✅ 精准替换成功!", diff_str🕹️ 在 Gradio 中动手体验
在 code/app.py 中切换到 8.5 终端执行与代码编辑 标签页:
- 终端执行测试:输入命令
python3 --version,查看标准输出、标准错误与退出码; - 精准代码替换测试:
- 目标文件:
sample_code.py - 原始文本:
name = 'world' - 替换文本:
name = 'Vibe Coding' - 点击 ✂️ 执行精准替换并生成 Diff,即刻查验生成的清晰代码比对视图!
- 目标文件:
diff
--- a/sample_code.py
+++ b/sample_code.py
@@ -1,3 +1,3 @@
def greet():
- name = 'world'
+ name = 'Vibe Coding'
print(f'hello {name}')🗂️ 进阶深化:补全"文件系统全家桶"(参考 deepagents 文件系统后端)
现实工程中,Agent 光会"看一个文件、改一个文件"远远不够,它还需要像人类开发者一样先侦察再动手:deepagents 把 ls / glob / grep 也纳入了文件系统工具集,让 Agent 能快速"俯瞰"整个项目。我们用三个极简工具补全手脚:
python
@registry.register
def list_dir(path: str = ".") -> str:
"""列出目录下所有文件名与类型,帮助 Agent 快速俯瞰项目"""
return "\n".join(f" - {f.name}" + ("/" if f.is_dir() else "") for f in os.scandir(path))
@registry.register
def grep_code(keyword: str, path: str = ".") -> str:
"""在项目中递归搜索关键词,返回文件路径与行号"""
hits = []
for root, _, files in os.walk(path):
for fname in files:
if fname.endswith((".py", ".md", ".json")):
for i, line in enumerate(open(os.path.join(root, fname), encoding="utf-8"), 1):
if keyword in line:
hits.append(f"{os.path.join(root, fname)}:{i}: {line.strip()[:60]}")
return "\n".join(hits[:20]) or "未找到匹配"有了 view_file + str_replace + list_dir + grep_code 四件套,Agent 才真正拥有"先侦察、再微创"的完整手术能力。
文件修改要能核对,也要能恢复
执行命令前应固定工作目录,并为命令设置超时、输出上限和环境变量白名单。编辑文件前先读取当前内容,修改后立即生成差异;如果匹配内容不唯一,应停止并要求更精确的定位,不能猜测要替换哪一处。
文本编码、换行符和符号链接也会影响安全边界。写入时可先生成临时文件,检查成功后再替换目标文件,避免进程中断留下半个文件。批量修改先在少量样本上验证,再扩大范围,更容易发现错误模式。
📝 本节小结
- 终端执行与超时守护:使用
subprocess加上timeout机制;它仍运行在宿主机上,属于“带门禁的本机 Shell”,不是容器沙箱; - 文件边界:读取与编辑都限制在工作区真实路径内,阻止
../和符号链接逃逸; - 微创手术级编辑:
str_replace凭借唯一性匹配与 Diff 反馈,成为现代 Coding Agent 最稳定可靠的代码修改引擎; - 新挑战:Agent 既然拥有了执行 Shell 和修改代码的巨大权力,如何防止它误删文件或执行破坏性操作?请进入下一节——8.6 权限控制与人类在环!