你所不知的 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 上工做,由于在这一点上他已经深深地投入了,并且他也只是单纯地喜欢把有用的软件提供给世界。

相关文章
相关标签/搜索