你所不知的 GNU Readline
有時我會覺得自己的計算機是一棟非常大的房子,我每天都會訪問這棟房子,也對一樓的大部分房間都了如指掌,但仍然還是有我沒有去過的卧室,有我沒有打開過的衣櫃,有我沒有探索過的犄角旮旯。我感到有必要更多地了解我的計算機了,就像任何人都會覺得有必要看看自己家裡從未去過的房間一樣。
GNU Readline 是個不起眼的小軟體庫,我依賴了它多年卻沒有意識到它的存在,也許有成千上萬的人每天都在不經意間使用它。如果你用 Bash shell 的話,每當你自動補全一個文件名,或者在輸入的一行文本中移動游標,以及搜索之前命令的歷史記錄時,你都在使用 GNU Readline;當你在 Postgres(psql
)或是 Ruby REPL(irb
)的命令行界面中進行同樣的操作時,你依然在使用 GNU Readline。很多軟體都依賴 GNU Readline 庫來實現用戶所期望的功能,不過這些功能是如此的輔助與不顯眼,以至於在我看來很少有人會停下來去想它是從哪裡來的。
GNU Readline 最初是自由軟體基金會在 20 世紀 80 年代創建的,如今作為每個人的基礎計算設施的重要的、甚至看不見的組成部分的它,由一位志願者維護。
充滿特色
GNU Readline 庫的存在,主要是為了增強各種命令行界面,它提供了一組通用的按鍵,使你可以在一個單行輸入中移動和編輯。例如,在 Bash 提示符中按下 Ctrl-A
,你的游標會跳到行首,而按下 Ctrl-E
則會跳到行末;另一個有用的命令是 Ctrl-U
,它會刪除該行中游標之前的所有內容。
有很長一段時間,我通過反覆敲擊方向鍵來在命令行上移動,如今看來這十分尷尬,也不知道為什麼,當時的我從來沒有想過可以有一種更快的方法。當然了,沒有哪一個熟悉 Vim 或 Emacs 這種文本編輯器的程序員願意長時間地擊打方向鍵,所以像 Readline 這樣的東西必然會被創造出來。在 Readline 上可以做的絕非僅僅跳來跳去,你可以像使用文本編輯器那樣編輯單行文本——這裡有刪除單詞、單詞換位、大寫單詞、複製和粘貼字元等命令。Readline 的大部分按鍵/快捷鍵都是基於 Emacs 的,它基本上就是一個單行文本版的 Emacs 了,甚至還有錄製和重放宏的功能。
我從來沒有用過 Emacs,所以很難記住所有不同的 Readline 命令。不過 Readline 有著很巧妙的一點,那就是能夠切換到基於 Vim 的模式,在 Bash 中可以使用內置的 set
命令來這樣做。下面會讓 Readline 在當前的 shell 中使用 Vim 風格的命令:
$ set -o vi
該選項啟用後,就可以使用 dw
等命令來刪除單詞了,此時相當於 Emacs 模式下的 Ctrl-U
的命令是 d0
。
我第一次知道有這個功能的時候很興奮地想嘗試一下,但它對我來說並不是那麼好用。我很高興知道有這種對 Vim 用戶的讓步,在使用這個功能上你可能會比我更幸運,尤其是你還沒有使用 Readline 的默認按鍵的話;我的問題在於,我聽說有基於 Vim 的界面時已經學會了幾種默認按鍵,因此即使啟用了 Vim 的選項,也一直在錯誤地用著默認的按鍵;另外因為沒有某種指示器,所以 Vim 的模態設計在這裡會很尷尬——你很容易就忘記了自己處於哪個模式,就因為這樣,我卡在了一種雖然使用 Vim 作為文本編輯器,但卻在 Readline 上用著 Emacs 風格的命令的情況里,我猜其他很多人也是這樣的。
如果你覺得 Vim 和 Emacs 的鍵盤命令系統詭異而神秘(這並不是沒有道理的),你可以按照喜歡的方式自定義 Readline 的鍵綁定。Readline 在啟動時會讀取文件 ~/.inputrc
,它可以用來配置各種選項與鍵綁定,我做的一件事是重新配置了 Ctrl-K
:通常情況下該命令會從游標處刪除到行末,但我很少這樣做,所以我在 ~/.inputrc
中添加了以下內容,把它綁定為直接刪除整行:
Control-k: kill-whole-line
每個 Readline 命令(文檔中稱它們為 「函數」 )都有一個名稱,你可以用這種方式將其與一個鍵序列聯繫起來。如果你在 Vim 中編輯 ~/.inputrc
,就會發現 Vim 知道這種文件類型,還會幫你高亮顯示有效的函數名,而不高亮無效的函數名。
~/.inputrc
可以做的另一件事是通過將鍵序列映射到輸入字元串上來創建預製宏。Readline 手冊給出了一個我認為特別有用的例子:我經常想把一個程序的輸出保存到文件中,這意味著我得經常在 Bash 命令中追加類似 > output.txt
這樣的東西,為了節省時間,可以把它做成一個 Readline 宏:
Control-o: "> output.txt"
這樣每當你按下 Ctrl-O
時,你都會看到 > output.txt
被添加到了命令行游標的後面,這樣很不錯!
不過你可以用宏做的可不僅僅是為文本串創建快捷方式;在 ~/.inputrc
中使用以下條目意味著每次按下 Ctrl-J
時,行內已有的文本都會被 $(
和 )
包裹住。該宏先用 Ctrl-A
移動到行首,添加 $(
,然後再用 Ctrl-E
移動到行尾,添加 )
:
Control-j: "C-a$(C-e)"
如果你經常需要像下面這樣把一個命令的輸出用於另一個命令的話,這個宏可能會對你有幫助:
$ cd $(brew --prefix)
~/.inputrc
文件也允許你為 Readline 手冊中所謂的 「變數」 設置不同的值,這些變數會啟用或禁用某些 Readline 行為,你也可以使用這些變數來改變 Readline 中像是自動補全或者歷史搜索這些行為的工作方式。我建議開啟的一個變數是 revert-all-at-newline
,它是默認關閉的,當這個變數關閉時,如果你使用反向搜索功能從命令歷史記錄中提取一行並編輯,但隨後又決定搜索另一行,那麼你所做的編輯會被保存在歷史記錄中。我覺得這樣會很混亂,因為這會導致你的 Bash 命令歷史中出現從未運行過的行。所以在你的 ~/.inputrc
中加入這個:
set revert-all-at-newline on
在你用 ~/.inputrc
設置了選項或鍵綁定以後,它們會適用於任何使用 Readline 庫的地方,顯然 Bash 也包括在內,不過你也會在其它像是 irb
和 psql
這樣的程序中受益。如果你經常使用關係型資料庫的命令行界面,一個用於插入 SELECT * FROM
的 Readline 宏可能會很有用。
Chet Ramey
GNU Readline 如今由凱斯西儲大學的高級技術架構師 Chet Ramey 維護,Ramey 同時還負責維護 Bash shell;這兩個項目都是由一位名叫 Brian Fox 的自由軟體基金會員工在 1988 年開始編寫的,但從 1994 年左右開始,Ramey 一直是它們唯一的維護者。
Ramey 通過電子郵件告訴我,Readline 遠非一個原創的想法,它是為了實現 POSIX 規範所規定的功能而被創建的,而 POSIX 規範又是在 20 世紀 80 年代末被制定的。許多早期的 shell,包括 Korn shell 和至少一個版本的 Unix System V shell,都包含行編輯功能。1988 年版的 Korn shell(ksh88
)提供了 Emacs 風格和 Vi/Vim 風格的編輯模式。據我從手冊頁中得知,Korn shell 會通過查看 VISUAL
和 EDITOR
環境變數來決定你使用的模式,這一點非常巧妙。POSIX 中指定 shell 功能的部分近似於 ksh88
的實現,所以 GNU Bash 也要實現一個類似的靈活的行編輯系統來保持兼容,因此就有了 Readline。
Ramey 第一次參與 Bash 開發時,Readline 還是 Bash 項目目錄下的一個單一的源文件,它其實只是 Bash 的一部分;隨著時間的推移,Readline 文件慢慢地成為了獨立的項目,不過直到 1994 年(Readline 2.0 版本發布),Readline 才完全成為了一個獨立的庫。
Readline 與 Bash 密切相關,Ramey 也通常把 Readline 與 Bash 的發布配對,但正如我上面提到的,Readline 是一個可以被任何有命令行界面的軟體使用的庫,而且它真的很容易使用。下面是一個例子,雖然簡單,但這就是在 C 程序中使用 Readline 的方法。向 readline()
函數傳遞的字元串參數就是你希望 Readline 向用戶顯示的提示符:
#include <stdio.h>
#include <stdlib.h>
#include "readline/readline.h"
int main(int argc, char** argv)
{
char* line = readline("my-rl-example> ");
printf("You entered: "%s"n", line);
free(line);
return 0;
}
你的程序會把控制權交給 Readline,它會負責從用戶那裡獲得一行輸入(以這樣的方式讓用戶可以做所有花哨的行編輯工作),一旦用戶真正提交了這一行,Readline 就會把它返回給你。在我的庫搜索路徑中有 Readline 庫,所以我可以通過調用以下內容來鏈接 Readline 庫,從而編譯上面的內容:
$ gcc main.c -lreadline
當然,Readline 的 API 比起那個單一的函數要豐富得多,任何使用它的人都可以對庫的行為進行各種調整,庫的用戶(開發者)甚至可以添加新的函數,來讓最終用戶可以通過 ~/.inputrc
來配置它們,這意味著 Readline 非常容易擴展。但是據我所知,即使是 Bash ,雖然事先有很多配置,最終也會像上面的例子一樣調用簡單的 readline()
函數來獲取輸入。(參見 GNU Bash 源代碼中的這一行,Bash 似乎在這裡將獲取輸入的責任交給了 Readline)。
Ramey 現在已經在 Bash 和 Readline 上工作了二十多年,但他的工作卻從來沒有得到過報酬 —— 他一直都是一名志願者。Bash 和 Readline 仍然在積極開發中,儘管 Ramey 說 Readline 的變化比 Bash 慢得多。我問 Ramey 作為這麼多人使用的軟體唯一的維護者是什麼感覺,他說可能有幾百萬人在不知不覺中使用 Bash(因為每個蘋果設備都運行 Bash),這讓他擔心一個破壞性的變化會造成多大的混亂,不過他已經慢慢習慣了所有這些人的想法。他還說他會繼續在 Bash 和 Readline 上工作,因為在這一點上他已經深深地投入了,而且他也只是單純地喜歡把有用的軟體提供給世界。
你可以在 Chet Ramey 的網站上找到更多關於他的信息。
喜歡這篇文章嗎?我會每四周寫出一篇像這樣的文章。關注推特帳號 @TwoBitHistory 或者訂閱 RSS 來獲取更新吧!
via: https://twobithistory.org/2019/08/22/readline.html
作者:Two-Bit History 選題:lujun9972 譯者:rakino 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive