你所不知的 GNU Readline

有時我會以爲本身的計算機是一棟很是大的房子,我天天都會訪問這棟房子,也對一樓的大部分房間都瞭如指掌,但仍然仍是有我沒有去過的臥室,有我沒有打開過的衣櫃,有我沒有探索過的犄角旮旯。我感到有必要更多地瞭解個人計算機了,就像任何人都會以爲有必要看看本身家裏從未去過的房間同樣。php

GNU Readline 是個不起眼的小軟件庫,我依賴了它多年卻沒有意識到它的存在,也許有成千上萬的人天天都在不經意間使用它。若是你用 Bash shell 的話,每當你自動補全一個文件名,或者在輸入的一行文本中移動光標,以及搜索以前命令的歷史記錄時,你都在使用 GNU Readline;當你在 Postgres(psql)或是 Ruby REPL(irb)的命令行界面中進行一樣的操做時,你依然在使用 GNU Readline。不少軟件都依賴 GNU Readline 庫來實現用戶所指望的功能,不過這些功能是如此的輔助與不顯眼,以致於在我看來不多有人會停下來去想它是從哪裏來的。html

GNU Readline 最初是自由軟件基金會在 20 世紀 80 年代建立的,現在做爲每一個人的基礎計算設施的重要的、甚至看不見的組成部分的它,由一位志願者維護。git

充滿特點

GNU Readline 庫的存在,主要是爲了加強各類命令行界面,它提供了一組通用的按鍵,使你能夠在一個單行輸入中移動和編輯。例如,在 Bash 提示符中按下 Ctrl-A,你的光標會跳到行首,而按下 Ctrl-E 則會跳到行末;另外一個有用的命令是 Ctrl-U,它會刪除該行中光標以前的全部內容。程序員

有很長一段時間,我經過反覆敲擊方向鍵來在命令行上移動,現在看來這十分尷尬,也不知道爲何,當時的我歷來沒有想過能夠有一種更快的方法。固然了,沒有哪個熟悉 Vim 或 Emacs 這種文本編輯器的程序員願意長時間地擊打方向鍵,因此像 Readline 這樣的東西必然會被創造出來。在 Readline 上能夠作的絕非僅僅跳來跳去,你能夠像使用文本編輯器那樣編輯單行文本——這裏有刪除單詞、單詞換位、大寫單詞、複製和粘貼字符等命令。Readline 的大部分按鍵/快捷鍵都是基於 Emacs 的,它基本上就是一個單行文本版的 Emacs 了,甚至還有錄製和重放宏的功能。github

我歷來沒有用過 Emacs,因此很難記住全部不一樣的 Readline 命令。不過 Readline 有着很巧妙的一點,那就是可以切換到基於 Vim 的模式,在 Bash 中可使用內置的 set 命令來這樣作。下面會讓 Readline 在當前的 shell 中使用 Vim 風格的命令:web

$ set -o vi

該選項啓用後,就可使用 dw 等命令來刪除單詞了,此時至關於 Emacs 模式下的 Ctrl-U 的命令是 d0sql

我第一次知道有這個功能的時候很興奮地想嘗試一下,但它對我來講並非那麼好用。我很高興知道有這種對 Vim 用戶的讓步,在使用這個功能上你可能會比我更幸運,尤爲是你尚未使用 Readline 的默認按鍵的話;個人問題在於,我據說有基於 Vim 的界面時已經學會了幾種默認按鍵,所以即便啓用了 Vim 的選項,也一直在錯誤地用着默認的按鍵;另外由於沒有某種指示器,因此 Vim 的模態設計在這裏會很尷尬——你很容易就忘記了本身處於哪一個模式,就由於這樣,我卡在了一種雖然使用 Vim 做爲文本編輯器,但卻在 Readline 上用着 Emacs 風格的命令的狀況裏,我猜其餘不少人也是這樣的。shell

若是你以爲 Vim 和 Emacs 的鍵盤命令系統詭異而神祕(這並非沒有道理的),你能夠按照喜歡的方式自定義 Readline 的鍵綁定。Readline 在啓動時會讀取文件 ~/.inputrc,它能夠用來配置各類選項與鍵綁定,我作的一件事是從新配置了 Ctrl-K:一般狀況下該命令會從光標處刪除到行末,但我不多這樣作,因此我在 ~/.inputrc 中添加了如下內容,把它綁定爲直接刪除整行:數據庫

Control-k: kill-whole-line

每一個 Readline 命令(文檔中稱它們爲 「函數」 )都有一個名稱,你能夠用這種方式將其與一個鍵序列聯繫起來。若是你在 Vim 中編輯 ~/.inputrc,就會發現 Vim 知道這種文件類型,還會幫你高亮顯示有效的函數名,而不高亮無效的函數名。bash

~/.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 上工做,由於在這一點上他已經深深地投入了,並且他也只是單純地喜歡把有用的軟件提供給世界。

相關文章
相關標籤/搜索