AI 辅助开发

使用 AI 代理作为你的 Shell 前端,而不是更多别名

被遗忘的 shell 脚本坟场有了替代方案。用简单的语言描述任务,让 AI 为你组合 `grep`、`awk` 和 `jq`。

本文由 AI 模型从英文原文翻译而来,措辞可能与原文有出入。 阅读英文原文

多年来,对于一个重复的命令,解决方法就是将其保存下来:一个别名、一个 shell 函数、一个放在 ~/bin 目录下且名字你日后会忘记的脚本。现在,这个习惯值得我们重新审视。当一个 AI 代理能够读取纯英文并当场组装出正确的标准工具管道时,大部分个人脚本层就不再有其价值了。这篇文章将探讨这种转变在哪些方面成立,在哪些方面不成立,以及决定其适用与否的规则。

这个论断的范围很窄。脚本并未过时。但是,人们编写的一大部分小型脚本都是用于一次性或很少重复的任务,之所以要将它们写成脚本,仅仅是因为手动输入管道命令很烦人。而这种烦恼恰恰是 AI 前端所能消除的。

被遗忘脚本的墓地

看看任何长期存在的 ~/bin.zshrc,你会发现同样的模式。几十个别名和函数,其中一半未使用,大部分未被记录。你记得 gsgit status,但那个用于跟踪日志的脚本叫 errlog 还是 logtail?六个月前,你写了一个脚本,用于从访问日志中提取失败的请求。再次找到它所花的时间比重写它还要长。

这里累积了三种成本。首先,你必须记住名称和参数顺序,这是一个查找问题,而你通过创造更多需要查找的东西来解决它。其次,你必须维护它们,因为一个标志改变了或一个路径移动了,脚本就会悄无声息地损坏。第三,没有其他人能读懂它们,所以这些知识只存在于一个人的头脑中,当那个人离开团队或忘记自己的简写时,这些知识也就随之消亡。

编写这些脚本本身并没有错。每一个都解决了当下的一个实际痛点。问题在于日积月累。个人自动化层的增长速度超过了任何人的修剪速度,其中大部分在一年内就成了无用的累赘。

通过 AI 驱动 shell 是什么样子的

另一种方法是陈述意图,让代理来构建命令。你不需要指定工具或回想某个标志。你描述你想要的结果,然后代理会选择 grepawkjqfindsort 或任何合适的工具,运行它,并向你显示输出。如果第一次尝试有偏差,你可以用自然语言而不是语法来纠正它。

它的形式如下,左边是意图,右边是代理组装的管道命令。

你的指令 代理运行的命令
从上周的日志中提取错误,并用三行话总结它们 grep -i error app.log 按日期范围过滤,然后代理读取并总结
现在哪些进程占用的内存最多 ps aux | sort -nrk4 | head
统计访问日志中唯一的 IP 地址数量 awk '{print $1}' access.log | sort -u | wc -l
这里每个子文件夹有多大,按从大到小排序 du -sh */ | sort -rh
查找本月添加的每一条 TODO 注释 git log --since='1 month ago' -p | grep '+.*TODO'
重命名这些文件,将空格转换成连字符 一个带有 mv "$f" "${f// /-}"for 循环
显示此目录下最大的 10 个文件 find . -type f -printf '%s %p\n' | sort -nr | head

注意左边是什么:都是些简单的请求,你无需记忆任何一个。右边的工具是经验丰富的 shell 用户已经知道的,但在时间压力下知道这些工具和回想起确切的标志是两种不同的技能。代理负责回想的部分。你保留对输出是否正确的判断。

总结的案例是原始脚本无法比拟的。grep 可以提取出错误行,但将一百个堆栈跟踪转换成三句话来说明实际失败的原因,这是一项语言工作。在同一个循环中,管道命令加上语言模型可以做到两者单独都无法完成的事情。

为什么说标准工具是 AI 的主场

这之所以行得通,是因为我们要求 AI 去做的事情。Unix 工具集小而陈旧,并且有极其详尽的文档。grepsedawkfindsortjq 以及将它们粘合在一起的 shell 几十年来一直很稳定,并有数百万个示例进行描述。当模型用这些部件组装流水线时,它是在其掌握的覆盖最全面的领域之一中工作。这些命令在设计上是可组合的,因此模型只是将已知的部件拼接在一起,而不是在发明任何东西。

与之相比,如果要求模型编写一个新颖的算法或驾驭一个私有的、无文档的内部 API。在这种情况下,它几乎没有立足之地,错误率也会攀升。Shell 组合则处于另一个极端。其构建模块是公开的,组合规则很简单,而且错误的猜测通常会以响亮而廉价的方式失败,而不是悄无声息地失败。

这其中还有第二个契合的原因。任务越是一次性且多变,脚本的效果就越差,而委托的效果就越好。只有当完全相同的操作重复次数足够多,足以分摊编写和维护脚本的成本时,脚本才物有所值。大多数 shell 需求并非如此。它们每次都略有不同:不同的日期范围、不同的字段、不同的文件。自然语言可以免费吸收这些变化,而脚本则需要为每个变体添加新的标志或重写。

脚本依然胜出的场景

委托并非解决所有问题的答案,而假装它是则会导致相反的错误。在一些明确的情况下,编写好的脚本要优于自然语言请求。

  • 高频重复。如果你每天要运行同一个操作二十次,那么每次用语言描述它的来回过程会比使用一个双字符的别名更慢。固定的、频繁的、完全相同的操作正是别名(alias)的用武之地。保留这些别名。
  • 精确的可复现性。当同一个命令每次都必须以相同的方式运行时,无论是在 cron 作业、部署步骤还是 CI 流水线中,你都希望将字面命令固定在文件中,而不是通过一个可能会产生细微差异的提示来重新组合。
  • 对正确性要求严苛的流水线。任何细微的变化都可能损坏数据或产生错误结果的操作,都应该放在经过评审和版本控制的脚本中。其价值不在于方便,而在于其精确的步骤是固定的且可审计的。
  • 共享的、命名的工作流。整个团队都依赖的部署步骤应该有一个带名称的规范形式,而不是存在于每个人的不同措辞中。

其模式很简单。脚本适用于那些稳定的、频繁的、且绝不能变化的任务。委托适用于那些探索性的、偶尔的、且每次都不同的任务。那句老建议,“你应该把它变成一个脚本”,在大多数重复性工作都属于第一类情况时是正确的。而当工作是一个动态变化的目标,从不以同样的方式重复两次时,这句建议就是错误的。

危险命令问题

将命令组装交给 AI 会带来一个明显的风险:模型可能会构建错误的命令,而一些错误的命令会删除或覆盖数据。这是真实存在的,它为整个方法设定了边界。

其防护措施与一位谨慎的工程师已经使用的相同。破坏性操作在运行前需要经过人工审查。一个会删除文件、覆盖数据库、强制推送分支或更改权限的请求,绝不应仅凭模型的指令就执行。你应该先阅读命令,确认它执行的是你想要的操作,然后才让它运行。一个好的智能体会呈现命令,并在遇到任何不可逆操作时暂停,而不是盲目执行。

只读和易于撤销的工作是可以自由委托的领域。列出、计数、搜索、总结和检查不会造成任何损害,因此几乎没有理由限制它们。思路上的划分在于区分观察性命令和变更性命令。观察性的,就让它运行。变更性的,则要三思而后行。

即使对于非破坏性工作,验证也很重要。模型可能会组装一个能干净利落运行但回答了错误问题的流水线,例如日期过滤器中差一的错误,或 awk 字段中错误的列。输出结果并不会因为是模型自信地生成的就自动正确。对待其结果,应像对待同事写的快速脚本一样:有用,可能正确,但在用于任何重要事情之前值得看一眼。

可复现性仍需记录

对话无法提供给你的一样东西是持久化的记录。如果一个任务重要到需要以同样的方式再次运行,或者需要交接给他人,或者需要在事后复盘中进行解释,那么你在聊天中输入的文字就并非可靠的产物。它们会消失或被淹没,而且模型下次可能会用不同的方式表述命令。

所以,诚实的做法并非“删除所有脚本”。而是记录某些事情的触发点改变了。你不再仅仅因为手动输入一次很繁琐而去编写任务脚本。你会在需要可复现性时才将其记录下来:当它需要无人值守运行时,当其他人依赖它时,当确切的步骤必须保留下来时。在那时,你才会将一次性的管道操作提升为一个真正的脚本或有文档记录的笔记,理想情况下,你可以要求智能体保存它刚刚运行的确切命令。智能体也擅长做这件事,一旦你决定某个成功的一次性操作值得拥有一个名字,它就能将其转变为一个有名称、已提交的脚本。

这保留了两者的优点。探索性和不常用的工作保留在自然语言中,快速且一次性。任何升级为承重(关键)部分的工作,都会像以前一样被固定在一个有名字的文件中。不同之处在于,真正需要完成这一跃迁的任务,要比旧习惯所认为的少得多。

我最终确定的规则

说明意图,不必考虑工具,只写下必须复现的内容。

对于那些占据了 shell 日常大部分时间的、持续不断的、探索性的、且每次都略有不同的工作来说,描述结果并让代理来构建管道命令,要比维护一个个人脚本博物馆更快、更轻量。而对于那些稳定的、频繁的、对正确性要求高的、共享的工作,则应该保留明确的脚本,因为在这种情况下,关键在于步骤是固定不变的。

区分任何任务的关键问题在于,它是否会以相同的方式重复。如果会,并且频繁重复,就将其固定下来。如果不会,就不要构建一个连自己都会忘记名字的工具。说出你想要的结果,在运行任何破坏性操作之前检查命令,然后让 shell 中那些最古老、文档最完善的工具去做它们一直在做的事情,只不过现在它们的前面多了一个翻译器。