本文为 Bash Reference Manual第8章:Command Line Editing ,第1节:Introduction to Line Editing 和 第2节 Readline Interaction 的读书笔记。
完整的笔记目录参见Bash学习笔记总目录。
本章描述了 GNU 命令行编辑界面的基本功能。命令行编辑由 Readline 库提供,该库被多个不同的程序使用,包括 Bash。在使用交互式 shell 时,命令行编辑默认是启用的,除非在启动 shell 时提供了 --noediting 选项。在使用 read 内置命令的 -e 选项时,也会使用命令行编辑(参见 Bash 内置命令)。默认情况下,命令行编辑命令类似于 Emacs;也提供了 vi 风格的命令行编辑界面。可以随时使用 set 内置命令的 -o emacs 或 -o vi 选项启用行编辑(参见 Set 内置命令),或者使用 set 的 +o emacs 或 +o vi 选项将其禁用。
bash
## 默认情况下,命令行编辑命令类似于 Emacs
$ shopt -o emacs
emacs on
8.1 Introduction to Line Editing
以下段落使用 Emacs 风格来描述表示按键的符号。
文本 C-k 读作"Control-K",表示在按下 Control 键的同时按下 k 键时产生的字符。
文本 M-k 读作"Meta-K",描述了当按下 Meta 键(如果有的话)并按下 k 键(一个元字符),然后同时松开时产生的字符。Meta 键在许多键盘上标记为 ALT 或 Option。在带有两个标记为 ALT 的键的键盘上(通常位于空格键的两侧),左侧的 ALT 通常设置为作为 Meta 键使用。其中一个 ALT 键也可能被配置为其他修饰键,例如用于输入带重音字符的 Compose 键。
在某些键盘上,Meta 键修饰符会生成第八位(0200)被设置的字符。如果键盘支持,可以使用 enable-meta-key 变量来控制是否启用此功能。在许多其他键盘上,终端或终端模拟器会将带有 Meta 的按键转换为以 ESC 开头的按键序列,如下一段所述。
如果你没有 Meta 键或 ALT 键,或者没有其他作为 Meta 键使用的键,你通常可以通过先按 ESC,然后再按 k 来实现后者的效果。ESC 字符被称为 meta 前缀。
无论哪种方式,都被称为将 k 键 metafying。
如果你的 Meta 键生成带有 ESC Meta 前缀的按键序列,你可以通过设置 force-meta-prefix 变量,让你指定的 M 键绑定(参见 Readline 初始化文件语法中的按键绑定)执行相同的操作。
文本 M-C-k 读作 'Meta-Control-k',表示由对 C-k 进行元化产生的字符。
此外,一些按键有它们自己的名称。具体来说,当在本文本中或在初始化文件中(参见 Readline 初始化文件)看到 DEL、ESC、LFD、SPC、RET 和 TAB 时,它们都表示它们自身。如果你的键盘没有 LFD 键(通常没有),按下 C-j 将输出相应的字符。在一些键盘上,RET 键可能标记为 Return 或 Enter。
bash
$ cat -v
^H <- 按键C-h
^H
^[h <- 按键M-h(即Alt+h)
^[h
^[^H <- 按键M-C-h
^[^H
从以上输出可知,^对应的是Control键,^[对应的是Meta键。
以下输出中,^对应的是Control键:
bash
$ stty -a
speed 38400 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc
8.2 Readline Interaction
在交互式会话中,你经常会输入一长行文本,却发现行首的一个单词拼写错误。Readline 库提供了一套在输入文本时操作文本的命令,使你只需修改拼写错误,而不必重新输入大部分内容 。使用这些编辑命令,你可以将光标移到需要修改的位置,然后删除或插入正确的文本。当你对这一行内容满意时,只需按下 RET(回车键)即可。你无需在行尾按 RET;无论光标位于行中的何处,整个行都会被接受。
8.2.1 Readline Bare Essentials
要在行中输入字符,只需键入它们。输入的字符会出现在光标所在的位置,然后光标向右移动一个空格。如果你输入错误的字符,可以使用删除键回退并删除错误输入的字符。
有时候,你可能会打错一个字符,但直到输入了几个其他字符后才注意到这个错误。遇到这种情况,你可以按 C-b 将光标向左移动,然后纠正你的错误。之后,你可以按 C-f 将光标向右移动。
当你在一行的中间添加文本时,你会注意到光标右侧的字符会被"推开",以为你插入的文本腾出空间。同样,当你删除光标前的文本时,光标右侧的字符会被"拉回",填补删除文本后产生的空白。
以下就是编辑输入行文本的基本要点:
-
C-b
向后移动一个字符。(backward,相当于向左键)
-
C-f
向前移动一个字符。(forward,相当于向右键)
-
DEL 或 Backspace
删除光标左侧的字符。
-
C-d (delete)
删除光标下方的字符。
-
输入字符
在光标所在行插入字符。
-
C-_ 或 C-x C-u (undo)
撤销上一次编辑命令。你可以一直撤销到空行为止。
根据您的配置,退格键可能设置为删除光标左侧的字符,而DEL键则设置为删除光标下方的字符,就像C-d一样,而不是删除光标左侧的字符。
8.2.2 Readline Movement Commands
上表描述了编辑输入行所需的最基本按键。为了方便起见,除了 C-b、C-f、C-d 和 DEL 外,还有许多其他命令可用。以下是一些用于在行内更快速移动的命令。
-
C-a
移动到行首。
-
C-e
移动到行尾。
-
M-f
向前移动一个单词,一个单词由字母和数字组成。
-
M-b
向后移动一个单词。
-
C-l
清屏,并将当前行重新打印在顶部。
注意,C-f 会向前移动一个字符,而 M-f 会向前移动一个单词。通常的约定是,控制键操作针对字符,而元键操作针对单词。
8.2.3 Readline Killing Commands
"Killing "文本意味着从行中删除文本,但将其保存起来以便以后使用,通常通过'yanking'(重新插入)将其放回行中。('剪切'和'粘贴'是比'kill'和'yank'更新的术语。)
如果某个命令的说明中说它会"kill"文本,那么你可以确定以后可以在不同(或相同)的地方将文本找回。
当你使用删除命令时,文本会被保存到删除环中。连续的多次删除会将所有删除的文本一起保存,这样当你回显它们时,可以一次性取回所有内容。删除环不是针对特定行的;你之前在某行中删除的文本,在你输入另一行时仍然可以回显取回。
以下是删除文本的命令列表。
-
C-k
删除从当前光标位置到行尾的文本。
-
M-d
删除从光标到当前单词末尾的内容,或者如果光标位于单词之间,则删除到下一个单词末尾。单词边界与 M-f 使用的相同。
-
M-DEL
删除从光标到当前单词开头的内容,或者如果光标位于单词之间,则删除到前一个单词开头。单词边界与 M-b 使用的相同。
-
C-w
删除从光标到前一个空白符的内容。这与 M-DEL 不同,因为单词边界不同。
下面是如何将文本拉回到行中的方法。拉取(yank)意味着将最近从剪切缓冲区删除的文本复制到当前光标位置的行中。
-
C-y
将最近删除的文本在光标处再次插入缓冲区。(如果上一个命令是kill,且光标位置不变,就相当于undo)
-
M-y
旋转剪切环,并插入新的顶部文本。你只能在前一个命令是 C-y 或 M-y 时执行此操作。
8.2.4 Readline Arguments
你可以向 Readline 命令传递数字参数。有时该参数表示重复次数,有时只是参数的符号有意义。如果你向通常向前执行的命令传递一个负参数,该命令将向后(向左)执行。例如,要删除到行首的文本,你可以输入 'M-- C-k'。
向命令传递数字参数的一般方法是在命令前输入元数字(meta digits)。如果输入的第一个"数字"是减号('-'),则参数的符号为负数。一旦你输入了一个元数字以开始参数,就可以输入剩余的数字,然后执行命令。例如,要给 C-d 命令传递参数 10,你可以输入 'M-1 0 C-d',这将删除输入行上的接下来的十个字符。
8.2.5 Searching for Commands in the History
Readline 提供了用于在命令历史记录中搜索包含指定字符串的命令的功能(参见 Bash 历史记录功能)。有两种搜索模式:增量搜索和非增量搜索。
增量搜索在用户完成输入搜索字符串之前就开始。当搜索字符串的每个字符被输入时,Readline 会显示历史记录中与当前已输入字符串匹配的下一个条目。增量搜索只需要输入找到所需历史记录条目所需的字符数。在使用 emacs 编辑模式时,输入 C-r 可以向后在历史记录中查找特定字符串。输入 C-s 会向前搜索历史记录。isearch-terminators 变量中存在的字符用于终止增量搜索。如果该变量没有被赋值,ESC 和 C-j 字符会终止增量搜索。C-g 会中止增量搜索并恢复原始行。搜索终止时,包含搜索字符串的历史记录条目将成为当前行。
💡 C-s 通常被定义为终端的stop键,因此会有向前搜索发生冲突。虽然不建议,但作为测试,可以先禁止stop键:
bash
# 显示默认配置
$ stty -a|grep -o "stop = [^;]*;"
stop = ^S;
# 临时禁用
$ stty stop undef
$ stty -a|grep -o "stop = [^;]*;"
stop = <undef>;
# 恢复为默认
$ stty stop ^S
要在历史记录列表中查找其他匹配条目,请根据需要输入 C-r 或 C-s。这会在历史记录中向后或向前搜索与已输入搜索字符串匹配的下一个条目。任何绑定到 Readline 命令的其他按键序列都会终止搜索并执行该命令。例如,按 RET 会终止搜索并接受该行,从而执行历史记录列表中的命令。移动命令将终止搜索,使找到的最后一行成为当前行,并开始编辑。
Readline 会记住上一次的增量搜索字符串。如果在没有输入任何中间字符以定义新搜索字符串的情况下连续输入两次 C-r,Readline 会使用记住的搜索字符串。
非增量搜索会在开始搜索匹配的历史条目之前读取整个搜索字符串。搜索字符串可以由用户输入,也可以是当前行内容的一部分。
非增量搜索用M-n和M-p开启,原文没有展开讲,可能是因为其很少用吧。
bash
$ bind -p | grep -i non-incremental
"\en": non-incremental-forward-search-history
# non-incremental-forward-search-history-again (not bound)
"\ep": non-incremental-reverse-search-history
# non-incremental-reverse-search-history-again (not bound)