終端機程式所遵循的「規則」
最近我一直在思考,終端機裡發生的一切,其實都是由以下幾者的組合所構成:
- 你的作業系統的工作
- 你的Shell的工作
- 你的終端機模擬器的工作
- 你正在執行的任何程式的工作(像是
top或vim或cat)
前三者(你的作業系統、Shell 與終端機模擬器)算是比較明確的要素——如果你在 Linux 上使用 GNOME Terminal 裡的 bash,大致上可以推論這些元件如何互動,而且它們的部分行為是由 POSIX 所標準化的。
但第四項(「你當下正在執行的任何程式」)感覺起來好像可以做任何事。你要怎麼知道一個程式會如何表現呢?
這篇文章有點長,所以先提供一個簡短的目錄:
程式的行為出乎意料地一致
就我所知,對於終端機中的程式應該如何表現,並沒有真正的標準——我所知道最接近的只有:
- POSIX,它主要規範了你的終端機模擬器/作業系統/Shell 應該如何協同運作。我想它確實有規範一些核心工具程式(像是
cp)應該如何運作,但據我所知,它對於例如htop這樣的程式應該如何表現則隻字未提。 - 這些命令列介面準則
但即使沒有標準,就我的經驗而言,終端機中的程式表現得相當一致。所以我想寫下一份清單,列出就我經驗中程式大多會遵循的「規則」。
這些是描述性的,而非規範性的
我在此的目的,並不是要說服終端機程式的作者們應該遵循這些規則。這些規則有很多例外,而且這些例外往往有其充分的理由。
但對我來說,能夠知道對於一個隨機的新終端機程式該預期什麼樣的行為,是非常有用的。與其想著「呃,程式什麼事都可能做」,不如想成「好,這裡有我預期的基本規則,然後我只要在心裡記住一份簡短的例外清單就好」。
所以我只是把我在 20 年的終端機使用經驗中觀察到的程式行為、我認為它們為何如此表現,以及一些「違反」該規則的案例寫下來。
並非總是很清楚哪些「規則」是由程式本身負責實作的
有一些常見的慣例,我認為很明顯是由程式本身負責實作的,例如:
- 設定檔應該放在
~/.BLAHrc或~/.config/BLAH/FILE或/etc/BLAH/這類地方 --help應該印出說明文字- 程式應該將「一般」輸出印到 stdout,並將錯誤訊息印到 stderr
但在這篇文章中,我想聚焦於那些並非百分之百顯而易見是由程式負責的事項。舉例來說,按下 Ctrl-D 就應該離開 REPL(互動式解譯環境)對我而言感覺像是「自然法則」,但程式往往需要明確地實作對它的支援——即使 cat 不需要實作對 Ctrl-D 的支援,ipython 卻需要。(更多內容請見下方的「規則 3」)
了解哪些事情是由程式負責的,就能讓你在看到不同程式的實作略有差異時,不會感到那麼意外。
規則 1:非互動式程式在你按下 Ctrl-C 時應該要結束
這個規則的主要原因在於,非互動式程式如果沒有設定 SIGINT 信號處理常式,在預設情況下按下 Ctrl-C 就會結束,所以這算是一種「你應該表現得像預設值一樣」的規則。
讓很多人困惑的一點是,這並不適用於像 python3 或 bc 或 less 這樣的互動式程式。這是因為在互動式程式中,Ctrl-C 有不同的任務——如果程式正在執行某個操作(例如在 less 中進行搜尋,或在 python3 中執行某些 Python 程式碼),那麼 Ctrl-C 會中斷該操作,但不會讓程式結束。
舉一個互動式程式如何運作的例子:這裡有在 prompt-toolkit 中(iPython 用來處理輸入的函式庫)的程式碼,它會在你按下 Ctrl-C 時中止搜尋。
規則 2:TUI(文字使用者介面)應該在你按下 q 時結束
TUI 程式(像是 less 或 htop)通常會在你按下 q 時結束。
這個規則不適用於任何按下 q 來結束並不合理的程式,像是 tmux 或文字編輯器。
規則 3:REPL 在空行上按下 Ctrl-D 時應該要結束
REPL(像是 python3 或 ed)通常會在你在空行上按下 Ctrl-D 時結束。這個規則與 Ctrl-C 的規則類似——原因在於,預設情況下如果你的程式(像是 cat)是在「cooked mode(處理模式)」下執行,那麼當你在空行上按下 Ctrl-D 時,作業系統會回傳一個 EOF。
我使用的大多數 REPL(sqlite3、python3、fish、bash 等)實際上並未使用 cooked mode,但它們都還是實作了這個鍵盤快速鍵來模擬預設行為。
舉例來說,這裡有在 prompt-toolkit 中的程式碼,它會在你按下 Ctrl-D 時結束,還有在 readline 中相同的程式碼。
直到最近以前,我其實一直以為這是「終端機物理定律」,因為我基本上從沒看過它被打破,但從上面的連結可以看到,這只是每個輸入函式庫各自必須實作的功能而已。
有人指出 Erlang 的 REPL 在你按下 Ctrl-D 時並不會結束,所以我想並非每個 REPL 都遵循這個「規則」。
規則 4:不要使用超過 16 種顏色
終端機程式很少會使用基本 16 種 ANSI 顏色以外的顏色。這是因為如果你用十六進位色碼來指定顏色,很有可能會與某些使用者的背景顏色衝突。舉例來說,如果我用 #EEEEEE 印出一些文字,在白色背景上幾乎會看不見,雖然在深色背景上看起來就沒問題。
但如果你堅持使用預設的 16 種基本顏色,使用者就很有可能已經在他們的終端機模擬器中設定好這些顏色,使其與背景顏色搭配得還算合適。另一個堅持使用預設 16 種基本顏色的原因,是這樣對於終端機模擬器支援哪些顏色所做的假設會比較少。
我通常看到會打破這個「規則」的程式只有文字編輯器,舉例來說,Helix 預設會使用紫色背景,這並非預設的 ANSI 顏色。Helix 打破這個規則似乎也沒什麼關係,因為 Helix 並不是「核心」程式,而且我想任何不喜歡該配色方案的 Helix 使用者都會直接更換主題。
規則 5:大致上支援 readline 按鍵綁定
我使用的幾乎每個程式,只要合理的話都會支援 readline 按鍵綁定。舉例來說,這裡有一堆不同的程式,以及它們定義 Ctrl-E 以跳至行尾的位置連結:
- ipython(Ctrl-E 定義於此)
- atuin(Ctrl-E 定義於此)
- fzf(Ctrl-E 定義於此)
- zsh(Ctrl-E 定義於此)
- fish(Ctrl-E 定義於此)
- tmux 的命令提示字元(Ctrl-E 定義於此)
這些程式實際上都沒有直接使用 readline,它們只是大致上模仿 emacs/readline 的按鍵綁定。它們並不總是完全照搬:例如 atuin 似乎將 Ctrl-A 用作前綴鍵,所以 Ctrl-A 並不會跳至行首。
而且這些程式似乎都實作了自己內部的剪下與貼上緩衝區,所以你可以用 Ctrl-U 刪除一行,然後用 Ctrl-Y 貼上。
例外情況包括:
- 有些程式(像是
git、cat和nc)完全沒有任何行編輯支援(除了退格鍵、Ctrl-W和Ctrl-U之外) - 和往常一樣,文字編輯器是個例外,每個文字編輯器都有自己編輯文字的方式
關於「程式支援哪些按鍵綁定?」這個問題,我在在終端機中輸入文字很複雜一文中寫了更多內容。
規則 5.1:Ctrl-W 應該刪除前一個單字
我從沒看過有哪個程式(文字編輯器除外)在按下 Ctrl-W 時不會刪除前一個單字。這與 Ctrl-C 的規則類似——預設情況下如果程式處於「cooked mode」中,作業系統會在你按下 Ctrl-W 時刪除前一個單字,並在你按下 Ctrl-U 時刪除整行。所以通常程式都會模仿這種行為。
除了文字編輯器之外,我想不到這個規則有任何例外,但如果有的話,我很樂意聽聽看!
規則 6:在寫入 pipe(管線)時停用顏色
大多數程式在寫入 pipe 時會停用顏色。舉例來說:
rg blah會在輸出中突顯所有出現的blah,但如果輸出是導向 pipe 或檔案,就會關閉突顯。ls --color=auto在寫入終端機時會使用顏色,但在寫入 pipe 時則不會
這兩個程式在寫入終端機時也會以不同的方式格式化輸出:ls 會將檔案整理成欄位,而 ripgrep 則會將相符項目以標題分組。
如果你想強制程式使用顏色(例如因為你想查看顏色),你可以使用 unbuffer 來強制讓程式的輸出成為 tty,像這樣:
unbuffer rg blah | less -R
我確定有些程式會「違反」這個規則,但我現在想不到任何例子。有些程式有 --color 旗標可以用來強制開啟顏色,在上面的例子中你也可以執行 rg --color=always | less -R。
規則 7:- 代表 stdin/stdout
通常如果你將 - 傳給程式來取代檔名,它會從 stdin 讀取或寫入至 stdout(視何者適用而定)。舉例來說,如果你想用 black 來格式化剪貼簿上的 Python 程式碼,然後再複製起來,你可以執行:
pbpaste | black - | pbcopy
(pbpaste 是一個 Mac 程式,你在 Linux 上可以用 xclip 做類似的事)
我的印象是,大多數程式只要合理的話都會實作這個功能,我現在想不到任何例外,但我確定有很多例外。
這些「規則」需要很長時間才能學會
這些規則花了我很長的時間才學會,因為我必須:
- 了解到這個規則在任何地方都適用(「
Ctrl-C會結束程式」) - 注意到一些例外(「好,
Ctrl-C會結束find,但不會結束less」) - 潛意識地弄清楚其中的模式(「
Ctrl-C通常會結束非互動式程式,但在互動式程式中,它可能會中斷目前的操作,而不是結束程式」) - 最終或許將其歸納為一個我所知的明確規則
老實說,我對終端機的很多理解仍處於「潛意識模式識別」的階段。我之所以花時間把這些東西明確寫出來,唯一的原因是我一直在嘗試向他人解釋它的運作方式。希望把這些「規則」明確地寫下來,能讓其他人學習這些東西的速度稍微快一些。
隨機一篇部落格