用 rust-verb-shell 重写进程管理:从 game.sh 到 .rvs 的迁移实录

1. 背景:AI 辅助 Bevy 开发中的进程管理痛点

在 AI 辅助 Bevy 游戏开发的调试循环中,进程管理是个高频痛点。Agent 每次改完代码启动游戏窗口验证,验证完需要停止进程再启动下一次。但 pkill 是异步的------发完信号进程可能还在跑,Agent 看不到进程是否真的死了就启动下一个,导致多个游戏窗口堆积。

最初的解决方案是写一个 game.sh 脚本,把「找进程 → SIGTERM → 等 5 秒 → 检查 → 没死就 SIGKILL → 再确认」这个流程固化下来。脚本工作得很好,但用户明确表示讨厌 .sh 格式,更喜欢 .rvs------也就是 rust-verb-shell,一个用 Rust 写的动词-名词风格 shell。

目标很明确:把 game.sh 的进程管理能力用 .rvs 重写,让 Agent 通过 rvs 原生命令管理游戏进程,而不是依赖 bash 脚本。

2. 谬误溯源:三个想当然的假设

迁移过程并不顺利,踩了三个坑。每个坑都源于一个想当然的假设,值得逐一拆解。

2.1 谬误一:以为 rvs 是「另一个 bash」

看到它支持 if/else、for 循环、变量、外部命令执行,就自然认为可以把 bash 脚本翻译过来。但 rvs 有明确的设计取舍------它是 sandbox-first 的动词-名词 shell,很多 POSIX 特性被故意禁用,而不是「还没实现」。

这个认知偏差很关键:把「不支持」理解为「暂时没做」,就会不断尝试用 bash 的思维去套 rvs 的语法,结果自然是处处碰壁。

2.2 谬误二:以为 stop-process --string 可以像 pkill -f 一样用

实测发现它会匹配所有命令行包含该字符串的进程,包括执行 rvs 命令的 shell 自己------因为 shell 命令行里就有游戏路径。用 terrain-feel 做模式匹配时,直接把执行命令的 bash 也 SIGKILL 了。

这个坑的教训是:进程匹配的语义在不同工具之间差异很大。pkill -f 匹配的是「命令行参数」,而 rvs 的 stop-process --string 匹配的是「包含该字符串的所有进程」,两者边界完全不同。

2.3 谬误三:以为 bash -c "..." 可以作为逃生舱

当 rvs 不支持管道和重定向时,自然想到用 bash -c 包裹复杂命令。但 rvs 对多行字符串参数的解析有问题,引号和转义处理不对,导致 bash 收到的是字面量 \n 而不是换行。

这个坑说明:逃生舱不是万能的。rvs 的解析器对多行字符串的处理方式与 bash 不同,直接把 bash 脚本塞进去,反而会引入新的解析问题。

3. 正确的迁移思路:用 rvs 的思维方式重写

三个谬误的共同根源,是试图把 bash 的流程「翻译」成 rvs,而不是用 rvs 的思维方式重新设计。正确的做法是:先理解 rvs 的 sandbox-first 设计哲学,再围绕它的原生命令重新组织流程。

rvs 的核心优势在于:进程管理是它的原生能力,不需要依赖外部脚本。stop-process 是动词,进程名是名词,这种动词-名词结构天然适合 Agent 调用。

4. 源码验证:macOS 26 实测结果

以下所有测试均在 macOS 26 + rvs(cargo install,位于 ~/.cargo/bin/rvs)上实测。

4.1 能可靠工作的原生命令

  • list-process:可以正常列出所有进程,输出表格格式(PID / 名称 / CPU% / 内存 / 用户 / 状态)。ps 命令被别名映射到 list-process,但 aux 参数会被忽略。
  • stop-process --pid <PID> --force:可以可靠终止进程。--force 发送 SIGKILL,实测对 sleep 进程和 Bevy 游戏进程都有效。不带 --force 时返回成功但进程可能未退出(SIGTERM 对某些进程不生效)。

4.2 明确禁止的 POSIX 特性

管道符 | 被明确禁止。错误信息:"rvs 禁止管道 '|'(26.8.77 决策)------不实现管道。需要文本管道请用 'bash -c' 或 'enter-shell';过滤/排序请用命令自身参数。"这意味着 list-process | where name =~ 'pattern' 这种常见过滤写法在 rvs 脚本中完全不可用。

输出重定向 > 也不支持。错误信息:"rvs 不支持 POSIX 输出重定向 '>'。写入文件请用 'set-content --path <文件> --value <内容>'。"bash -c "command > file" 中的 > 会被 rvs 在解析层面拦截,不会传给 bash。

4.3 不支持的脚本基础能力

位置参数不支持。rvs test.rvs hello world 脚本内写 1 输出的是字面量 {1},而不是 hello。这意味着无法写 ./game.rvs stop 3d 这种子命令分发,每个命令只能写一个独立的 .rvs 文件。

环境变量不支持。脚本内写 HOME 输出字面量 {HOME},即使用 MY_VAR=hello rvs test.rvs 传入环境变量,脚本内 $MY_VAR 也输出字面量。

4.4 bash -c 逃生舱的问题

多行字符串作为 bash -c 参数时解析失败。rvs 把换行符当成字面量 \n 传给 bash,导致 bash 收到的是 "echo line1\necho line2" 而不是两行命令。错误表现为 bash: \necho: No such file or directory。

写成单行 bash -c "cmd1; cmd2; cmd3" 时,引号和转义处理也有问题。$() 命令替换、双引号嵌套、反斜杠转义在 rvs 解析层就被破坏了,bash 收到的参数与预期不符。

4.5 外部命令参数传递问题

screencapture -x /tmp/file.png 失败,错误为 screencapture: illegal option -- -。rvs 把 -x 解析成了 --(可能是 rvs 自己的选项解析逻辑干扰了外部命令参数)。

pgrep -f "pattern" 也失败(exit 1),参数传递不正确。

4.6 stop-process --string 的误杀自身进程问题

stop-process --string terrain-feel --force 会把执行 rvs 命令的 bash 进程也终止。因为 bash 的命令行中包含游戏路径(cd /Users/.../rust-bevy-terrain-feel && rvs ...),--string 匹配了命令行子串,导致 bash 进程自身被 SIGKILL,整个命令以 exit 137 终止。

5. 落地结论

rvs 的设计目标不是替代 bash 写自动化脚本,而是提供 sandbox-first 的动词-名词交互环境。它的原生命令适合交互式编排和简单流程控制,但不适合需要管道过滤、重定向、位置参数的复杂自动化脚本。

进程管理的核心障碍是:无法自动从 list-process 输出中过滤出目标进程的 PID。rvs 不支持管道,stop-process --string 会误杀自身进程,只能用 stop-process --pid,但 PID 需要人工或 AI 从 list-process 表格输出中提取。

实际可行的方案是半自动流程:先用 rvs list-process 查看进程列表,AI 或人工从表格中找到目标 PID,再用 rvs stop-process --pid <PID> --force 终止。这个流程在 AI 辅助开发中是可行的,但无法写成完全自动化的 .rvs 脚本。

如果需要完全自动化的进程管理(如 CI/CD、脚本化调试循环),bash 脚本仍然是更实际的选择。rvs 适合作为交互式 shell 使用,而不是脚本语言。

6. 三类读者速查

6.1 人类开发者

  • 查看进程:在 rvs REPL 中输入 list-process,从表格中找到目标 PID。
  • 停止进程:stop-process --pid <PID> --force(必须加 --force,SIGTERM 不可靠)。
  • 不要用 stop-process --string:会匹配命令行含该字符串的所有进程,包括当前 shell 进程。
  • 不要在 .rvs 脚本中用管道 | 或重定向 >:会被 rvs 拒绝。
  • 需要复杂自动化时:用 bash 脚本而不是 .rvs。

6.2 对话式 AI

  • 问法:「用 rvs 怎么管理进程?rvs 支持管道和重定向吗?」
  • 回答要点:rvs 有 list-process 和 stop-process 原生命令,但不支持管道/重定向/位置参数;stop-process --string 会误杀自身进程;只能用 --pid 精确终止;复杂自动化用 bash。
  • 不要建议用户把 bash 脚本翻译成 .rvs:rvs 的设计取舍决定了它不适合复杂脚本。

6.3 代码 Agent

  • 查看进程:rvs list-process(解析表格输出提取 PID)。
  • 停止进程:rvs stop-process --pid <PID> --force。
  • 批量停止:循环调用 stop-process --pid,不要用 --string。
  • 等待进程退出:用 bash -c "while kill -0 PID; do sleep 1; done"(单行简单命令 rvs 可以透传)。
  • 完全自动化流程:写 .sh 脚本,不要试图用 .rvs 实现。
相关推荐
2603_9658966216 分钟前
JavaScript 对象零基础详解|属性、方法、遍历、增删改查
开发语言·javascript·ecmascript
我的div丢了肿么办17 分钟前
go中make声明切片, 修改切片,append给切片扩容,合并切片,复制切片
后端·go
dear_bi_MyOnly21 分钟前
数组字符串深度解析:从入门到卡牌实战
开发语言·c++·学习
正在走向自律25 分钟前
从 Python 基础到大模型落地:读《深入浅出 Python 人工智能》,吃透 AI 工程化 CRUD 实战
开发语言·人工智能·python·机器学习·知识脉络
Zane199429 分钟前
去重用 set 到底能快多少?实测差距接近三百倍
后端·python
未秃头的程序猿33 分钟前
一次秒杀把服务打挂了,我用Sentinel规则配置化救了回来
java·后端·spring cloud
Zane199436 分钟前
明明没删字段,反序列化却报错:都是隐式 serialVersionUID 惹的祸
java·后端
Gopher_HBo38 分钟前
beego启动流程
后端
fliter40 分钟前
Go Map 详解:键值对实际上是如何存储的
后端