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,數十年來一直很穩定,並有數百萬個範例加以描述。當模型從這些部分組裝出一個管線 (pipeline) 時,它是在其所擁有涵蓋最全面的領域之一中工作。這些指令在設計上是可組合的,所以模型只是將已知的片段組合在一起,而不是發明任何東西。

將此與要求模型編寫一個新穎的演算法,或操作一個私有、無文件記錄的內部 API 相比。在那些情況下,它幾乎沒有立足點,錯誤率也隨之攀升。Shell 組合則位於光譜的另一端。其建構模塊是公開的,組合規則很簡單,而且錯誤的猜測通常會大聲且廉價地失敗,而不是靜默地失敗。

還有第二個適合的原因。任務越是一次性且多變,腳本就越不適用,而委派則越能勝任。只有當完全相同的操作重複次數足夠多,足以攤銷編寫和維護腳本的成本時,腳本才划算。大多數的 shell 需求並非如此。它們每次都有些微不同:不同的日期範圍、不同的欄位、不同的檔案。自然語言免費地吸收了這些變化,而腳本則需要為每個變體添加新的旗標或進行重寫。

腳本依然佔優勢的情況

委派並非萬能的解決方案,而假裝它是萬能的則會導致相反的錯誤。在幾個明確的情況下,寫好的腳本勝過自然語言請求。

  • 高頻率的重複。如果你一天要執行相同的操作二十次,每次用文字描述它的來回過程,會比一個雙字元的別名 (alias) 來得慢。固定、頻繁、相同的操作正是別名 (alias) 的用途。保留那些別名吧。
  • 精確的可重現性。當同一個指令每次都必須以相同的方式執行時,無論是在 cron job、部署步驟或 CI 管線中,你會希望將字面上的指令固定在一個檔案裡,而不是從一個可能會產生些微差異的提示中重新組合。
  • 攸關正確性的管線。任何細微的變化都可能損壞資料或產生錯誤結果的情況,都應該放在經過審查、版本控制的腳本中。其價值不在於方便,而在於確切的步驟是固定的且可供審核。
  • 共享的、具名的工作流程。整個團隊都依賴的部署步驟,應該有一個帶有名稱的標準形式,而不是存在於每個人的措辭中。

這個模式很簡單。腳本適用於穩定、頻繁且不得變動的事物。委派則適用於探索性、偶爾為之且每次都不同的事物。那句老建議:「你應該把它變成一個腳本」,在大多數重複性工作都屬於第一類時是正確的。但當工作是一個永遠不會以相同方式重複兩次的移動目標時,這句話就是錯的。

危險指令問題

將指令組裝交給 AI 會帶來一個明顯的風險:模型可能會建構出錯誤的指令,而有些錯誤的指令會刪除或覆寫資料。這是真實存在的風險,也為整個方法劃定了界線。

其防護機制與一位謹慎的工程師所使用的並無二致。破壞性操作在執行前需經人工審核。一個會刪除檔案、覆寫資料庫、強制推送分支或變更權限的請求,絕不應該僅憑模型的說法就執行。你應先閱讀指令,確認它執行的是你所預期的操作,然後才讓它執行。一個好的代理程式會顯示指令內容,並在遇到任何不可逆的操作時暫停,而不是盲目地執行。

唯讀和易於還原的工作,是可以自由授權的領域。列出、計數、搜尋、摘要和檢查等操作不會造成任何損害,所以幾乎沒有理由去限制它們。心態上的區分在於「觀察型」指令和「變更型」指令之間。觀察型的,就讓它執行;變更型的,則三思而後行。

即使是非破壞性的工作,驗證依然重要。模型可以組裝出一個能順利運行的管線,但仍然回答了錯誤的問題,例如日期篩選器中的差一錯誤,或是在 awk 欄位中使用了錯誤的欄位。輸出結果並不會因為它是充滿自信地產生的就自動正確。對待其結果的方式,應該像對待同事寫的快速腳本一樣:有用,可能正確,但在用它處理任何重要事務之前,值得先看一眼。

可重現性依然需要紀錄

對話無法提供的一件事就是持久的紀錄。如果一項任務重要到需要以相同方式再次執行、交給他人,或是在事後檢討中解釋,那麼你在聊天中輸入的文字並不是一個可靠的產物。它們會消失、被淹沒,而且模型下次可能會用不同的方式表達指令。

所以,誠實的立場並不是「刪除你所有的腳本」。而是將事情寫下來的觸發點改變了。你不再僅僅因為打一次指令很繁瑣就為任務編寫腳本。你在需要可重現性時才將它寫下來:當它需要無人看管地運行、當其他人依賴它、當確切的步驟必須被保留下來時。在那個時候,你將用完即丟的管線(pipeline)提升為一個真正的腳本或一份有記錄的筆記,理想情況下是要求代理人儲存它剛才運行的確切指令。代理人也很擅長這點,一旦你決定它值得擁有一個名字,它就能將成功的一次性操作轉變為一個已命名、已提交的腳本。

這保留了兩者的優點。探索性和不常做的工作保留在自然語言中,快速且用完即丟。任何升級到需承擔重任的工作,都會像以前一樣,被固定在一個有名字的檔案中。不同之處在於,與舊習慣所設想的相比,需要進行這種跳躍的任務要少得多。

我最終定下的規則

說出意圖,將工具拋諸腦後,只寫下必須重現的內容。

對於那些佔據了 shell 日常大部分時間的、持續不斷、具探索性且每次都略有不同的工作,描述結果並讓代理程式組合管線,會比維護一個個人腳本博物館來得更快、更輕鬆。對於那些穩定、頻繁、正確性至關重要且需要共享的工作,請保留明確的腳本,因為重點在於步驟不會改變。

區分任何任務的問題在於它是否會以相同的方式重複。如果會,而且頻繁,就將它固定下來。如果不會,就不要建立一個你會忘記名稱的工具。要求最終結果,在執行任何破壞性操作前檢查指令,並讓 shell 中最古老、文件最齊全的工具去做它們一直在做的事,只是現在它們面前多了一個翻譯器。