Skip to content

8.5 终端执行与代码编辑:受控 Shell 与 str_replace 行级替换 ​

“修补一件衣服上的纽扣,绝不需要把整件衣服拆成碎布重新织一遍;外科医生做微创手术,靠的是毫米级的精准手术刀!”


⚙️ 赋予手脚:Coding Agent 的两大核心工具 ​

一个真正的 Coding Agent(如 Claude Code、Cursor、Devin)与聊天机器人最根本的区别,在于它能直接操作操作系统:

  1. 终端执行器 (run_bash):安装依赖、运行测试、查看进程、执行 Git 命令;
  2. 代码编辑器 (str_replace):读写文件、修改代码、精准重构。

⚙️ 赋予手脚:Coding Agent 的两大核心工具


🚫 为什么严禁让大模型全量重写文件? ​

早期 Agent 常常使用 write_file(path, full_content)。当面对 300 行以上的代码文件时,全量重写会带来三大灾难性后果:

  1. Token 消耗暴增:哪怕只改一行变量名,也要消耗几千 Token 重写全文件;
  2. 长文本生成截断:一旦触发 max_tokens 上限,文件末尾代码直接丢失;
  3. “幻觉吞代码”:大模型极易偷懒输出 // ... 其余逻辑保持不变 ...,直接把你的核心业务逻辑给清空覆盖了!

因此,顶尖编程工具 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 终端执行与代码编辑 标签页:

  1. 终端执行测试:输入命令 python3 --version,查看标准输出、标准错误与退出码;
  2. 精准代码替换测试:
    • 目标文件: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 权限控制与人类在环!