学习bash第二版-第二章 命令行编辑

  在计算机键盘上键入时总是会犯错误,而在使用一个UNIX shell时这种情况可能出现的更多。UNIX shell语法很强大,也相当简洁,但是有很多不常用的字符,不容易记忆,可以构建出既复杂又晦涩难懂的命令行。Bourne shell和C shell通过给编辑命令行加入大量的限制使这一点变得更加复杂化。
  特别是,没有可以重新调用前面键入的命令行以便修正错误的方式。如果你是一位资深的Bourne shell用户,毫无疑问你会遇到过重新键入冗长命令行的麻烦。有时可以使用BACKSPACE键来编辑,但一旦你键入了RETURN,就不可能再重来了。
  C shell通过其历史机制有了一些改进,它给出了一些很笨拙的编辑前面命令的方式,但许多人会疑惑,“为什么我不能像在编辑器里编辑文本那样编辑命令行呢?”
  这也正是bash实现了的功能。它拥有了允许你使用类似于两个最流行的UNIX编辑器vi和emacs的功能来编辑命令行的编辑模式。它还给出了类似于C shell历史机制的扩展产品,称为fc(即修正命令),它允许你直接使用喜爱的编辑器直接编辑命令行。除此之外,bash还提供了最初的C shell历史机制。
  本章讨论所有bash的命令历史功能通用的特性和每一功能的细节。如果你使用vi或emacs,就可以只阅读你所使用的那个编辑器的模拟编辑模式介绍。如果你不使用vi或emacs,而有兴趣学习这两种编辑模式中的一个,我们推荐emacs模式,因为它更接近于你最初接触的shell最小化编辑功能的本质性扩充。
  我们事先要声明,emacs模式和vi模式于UNIX终端接口设置的控制键存在潜在的不协调。回忆一下第一章bash基础中表1-7显示的控制键和stty命令输出示例,那里给出的控制键功能覆盖了它们在编辑模式下的功能。
  在本章其余部分,我们会在你使用默认的终端接口控制键设置与编辑命令发生冲突时给出警告。
**允许命令行编辑
  bash最初设置交互式emacs模式为默认模式(除非你启动bash时带有-noediting选项,详细内容请参见第十章)。在shell里有两种方式选择编辑模式,首先,可以使用set命令:
  $ set -o emacs
  或
  $ set -o vi
  第二种选择编辑模式的方式是在文件.inputrc中设置readline变量。本章后面会介绍这种方法。
  你会发现vi和emacs编辑模式都擅于模拟这些编辑器的基本命令,但不能模拟其高级特性;它们的主要作用是将流行编辑器的“键盘习惯”转到shell。fc有很强大的功能:它主要用来支持C shell历史,并作为使用vi或emacs之外其他编辑器用户的“应急出口”。因此fc一节主要面向C shell用户以及不使用这两种标准编辑器的人。
**历史文件
  所有的bash命令历史功能都依靠记录在shell中键入过的命令的列表。当登录或启动另一个交互式shell后,bash从用户根目录下文件.bash_history中读取最初的历史列表。从此时开始,每个bash交互式会话开始维护其命令列表。退出shell时,它会将列表保存到.bash_history中。通过设置环境变量HISTFILE你可以任意命名该文件。在下一章我们会详细讨论HISTFILE和其他相关的命令历史变量。
**emacs编辑模式
  如果你是emacs用户,就会发现将emacs编辑模式看做带有单独一行窗口的简化emacs非常合适。所有的基本命令,包括移动光标、剪切、粘贴以及搜索都是可用的。
**基本命令
  emacs模式使用控制键实现基本的编辑功能。如果你不熟悉emacs,可以将它们看作UNIX通过其用户终端接口提供的“erase”字符的扩充(通常是BACKSPACE或DEL)。为一致性起见,这里假定擦除字符是DEL,如果它是CTRL-H或其他,则需要进行替换。在表2-1中列出了最基本的控制键命令(记住当命令行为空时,键入CTRL-D会退出登录)。emacs模式的基本键盘习惯很容易学习,但需要你熟悉emacs编辑器特有的概念。
  
  表2-1     基本emacs模式命令
  命令      说明
  CTRL-B    向后移动一个字符(不删除)
  CTRL-F    向前移动一个字符
  DEL       向后删除一个字符
  CTRL-D    向前删除一个字符
  
  前几个是CTRL-B和CTRL-F向后和向前光标移动的用法,这些键的好处是很容易记忆。也可以使用左右光标移动键(“箭头”键)。但本书后面均使用控制键,因为它们对所有键盘都适用。在emacs模式中,point(有时也称dot)是在字符左面,即想像中光标所在的位置。在表2-1的命令说明中,有时说“向前”,有时说“向后”,可以将向前看作光标向右,向后看作光标向左。
  例如,假设你键入了一行,但还没有键入RETURN,键入CTRL-B并按住它使它不断重复,光标就会向左移动直至到达该行的第一个字符:
  $ [[f]]grep -l Duchess < ~cam/book/alice_in_wonderland
  现在光标位于f处,point位于行的开始。刚好在f前面。如果键入DEL,什么都不会发生,因为point左面没有字符。然而,如果按住CTRL-D(“向前删除”命令),就会删除第一个字符:
  $ [[g]]rep -l Duchess < ~cam/book/alice_in_wonderland
  point仍在行的最前面,如果这是所需的命令,就可以键入RETURN运行它,不必非要将光标移动到行的最后。然而,可以重复键入CTRL-F使光标移到行的最后:
  $ grep -l Duchess < ~cam/book/alice_in_wonderland[[ ]]
  此时,再按CTRL-D将什么也不执行,但点击DEL会删除最后的d。
**单词命令
  基本命令使你完全能够处理命令行的编辑,但高级命令集可以键入更少的键。这些命令对单词进行操作而不是单个字符,emacs模式将单词定义为一个或多个字母数字的序列。
  单词命令如表2-2所示。基本命令都是单个单词,而这里的命令由两次按键组成,ESC后跟一个字母。注意,命令ESC X,其中X是一个字符,它对单词所完成的功能通常与CTRL-X对单个字符所完成的功能类似。表中的“删除”是指无法恢复的删除。
  
  表2-2        emacs模式单词命令
  命令         说明
  ESC-B        向后移动一个单词
  ESC-F        向前移动一个单词
  ESC-DEL      向前删除一个单词
  ESC-CTRL-H   向前删除一个单词
  ESC-D        向后删除一个单词
  CTRL-Y       检索出(“恢复”)最后删除的项
  
  在上面的例子中,如果键入ESC-B,point将会向后移动一个单词,因为下划线(_)不是一个字母数字,emacs模式的光标将停在如下位置:
  $ grep -l Duchess < ~cam/book/alice_in_[[w]]onderland
  光标在wonderland的w处,point在_和w之间。现在要改变命令的-l选项,从Duchess改到Cheshire,需要在命令行上向后移,因此键入ESC-B 4次,结果为:
  $ grep -l Duchess < ~[[c]]am/book/alice_in_wonderland
  如果再键入ESC-B,则结束在Duchess的开始处:
  $ grep -l [[D]]uchess < ~cam/book/alice_in_wonderland
  为什么?记住单词被定义为字母数字序列,因此<不是一个单词;向后方向的下一个单词是Duchess。要在此位置删除Duchess,键入ESC-D,得到:
  $ grep -l [[ ]]< ~cam/book/alice_in_wonderland
  现在可以键入所需参数:
  $ grep -l Cheshire[[ ]]< ~cam/book/alice_in_wonderland
  如果要再变回Duchess,可以使用CTRL-Y命令,CTRL-Y命令将恢复最后一次被删除的单词。这里,CTRL-Y将在该位置插入Duchess。
**行命令
  emacs模式中包含了一些在命令行中移动更有效的命令。有些命令可对整行进行处理,如表2-3所示。
  
  表2-3    emacs模式行命令
  命令     说明
  CTRL-A   移到行开始处
  CTRL-E   移到行结尾处
  CTRL-K   向前删除到行尾
  
  使用CTRL-A、CTRL-E、CTRL-K很直接。记住,CTRL-Y总是恢复最后删除的内容,如果使用了CTRL-K,可能只删除几个字符。
**在历史文件中移动
  现在你已经在知道了如何有效的在命令行中移动以及进行改动,但这并没有解决我们最初提出的问题:如何通过访问历史文件再次调用前面的命令。emacs为此提供了几个命令,如表2-4所示。
  
  表2-4    emacs模式在历史文件中的移动命令
  命令     说明
  CTRL-P   移到前一行
  CTRL-N   移到后一行
  CTRL-R   向后搜索
  ESC-<    移到历史文件的第一行
  ESC->    移到历史文件的最后一行
  
  CTRL-P和CTRL-N使你可以在命令历史中移动。如果你有光标移动键(箭头键),可以转而使用它们。上箭头等同于CTRL-P,下箭头等同于CTRL-N。本书其余部分我们坚持使用控制键,因为它们对所有键盘适用。
  CTRL-P是最长用的一个——它的作用是“假如我犯了错误,可以让我再回过去找到它”。使用它可以随意在历史文件中向后移动。如果要回到输入的最后一条命令,可以按住CTRL-N,直到bash响铃为止,或是按ESC->。例如,输入RETURN运行上面的命令,但你得到一条错误信息告诉你选项字母不正确。要改变它不需要再次键入整条命令。
  首先,键入CTRL-P再次调出该错误命令,此时指针在行尾:
  $ grep -l Duchess < ~cam/book/alice_in_wonderland[[ ]]
  之后输入CTRL-A、ESC-F,两个CTRL-F和CTRL-D,则得到:
  $ grep -[[ ]]Duchess < ~cam/book/alice_in_wonderland
  使用-s选项来代替-l,因此键入s及RETURN。得到同样的错误信息,因此你决定放弃。查看帮助页,你才发现想要的命令是fgrep而不是grep。
  再回去寻找你以前键入的fgrep命令。为此,键入CTRL-R,当前行的内容将消失,而由(reverse-i-search)`':替代,键入fgrep,会看到:
  $ (reverse-i-search)`fgrep': fgrep -l Duchess <~cam/book/ \
   alice_in_wonderland[[ ]]
  每键入一个字母,shell都会动态查询命令历史,在前面的命令中查找当前的子字符串。这个例子中,键入f,shell会打印出历史中包含该字符的最近命令。如果键入多个字符,shell会缩小搜索范围,并结束在满足条件的最后输入的命令行上。当然,这可能不是你想要的特定行,可以键入CTRL-R继续向前在历史列表中搜索包含fgrep的行,如果shell没有发现子字符串,就会响铃。
  如果键入RETURN运行fgrep,会产生两个操作。首先当然是命令运行;第二,该行被输入到命令历史文件的最后,“当前行”将是最后一行。
  CTRL-P、CTRL-N和CTRL-R是用来处理命令历史的最重要的emacs模式命令。其他一些比较而言就没那么重要,但也包含在emacs编辑器兼容框架中。
**文字完成
  emacs模式一个最重要的特性是其文字完成功能(仍在用)。它受启发于完全emacs编辑器、C shell和最初的DEC TOPS-20操作系统中类似的特性。
  文字完成背后的想法很简单:你只需要键入一个文件名、用户名、函数等的部分文字就可以完全确定它们。这是一个很好的特性,在vi模式中也有类似的特性。建议你花时间学习它,因为它可以省去大量的键入工作。
  在emacs模式中有三个命令和文字完成有关,其中最重要的是TAB。当你键入一个单词后跟TAB时,bash将试图完成此名字,可能出现下面4种情况:
  1.如果没有匹配该名字的项目,shell将响铃并且不做任何事情。
  2.如果存在唯一匹配该字符串的搜索路径下的命令名、函数名或文件名,shell就会打印其余部分,后跟空格,以便你键入其他的命令参数。只有在单词位于一行的命令位置才会尝试命令名完成(例如,一行的开始)。
  3.如果存在唯一匹配字符串的一个目录,shell会完成其文件名,后跟一个斜线。
  4.如果有完成名字的多种方式,shell会在会给出所有选择的最长公共前缀。搜索路径下的命令名和函数名优先于文件名。
  例如,假定有一目录,包含文件tweedledee.c和tweedledum.c。键入cc tweedledee.c可编辑第一个文件。键入cc twee后跟TAB,这个前缀很模糊,因为两个文件名的前缀都是“twee”,shell则只匹配出cc tweedled。需要键入更多的字符以区分它们,因此需要再键入e并点击TAB,然后shell匹配出cc tweedledee.c,并给出额外的空格以键入其他文件名或选项。
  如果键入cc twee后不知道哪些选项可利用,可以再次按下TAB,bash将打印出所有可能的匹配并再次出现输入行:
  $ cc tweedled
  tweedledee.c  tweedledum.c
  $ cc tweedled
  相关命令是ESC-?,它会扩充所有可选择的前缀,并在标准输出中列出它们。记住,完成机制并非一定扩充到文件名。如果存在满足给出字符串的函数和命令,shell会首先扩充为它们,并忽略当前目录下的文件。正如下面你将看到的,可以强制完成为一特定类型。
  也有可能根据字符串完成其他环境实体。如果要被完成的文本前面是符号$,shell会试图扩充该命令为一shell变量名(见第三章对shell变量的讨论)。如果文本前缀为~,则试图扩充为用户名;如果前缀是符号@,则扩充为主机名。
  例如,假设系统有一用户名cameron,如果要更换到此用户的主目录,可以使用符号~ ,并键入名字的前几个字母,后跟TAB:
  $ cd ~ca
  将扩展成:
  $ cd ~cameron/
  可以强制shell完成指定的项。表2-5列出此功能的标准键。
  
  表2-5       完成命令
  命令        说明
  TAB         试图执行文本的一般性完成
  ESC-?       列出所有可能的完成
  ESC-/       试图进行文件名完成
  CTRL-X /    列出所有可能的文件名完成
  ESC-~       试图进行用户名完成
  CTRL-X ~    列出所有可能的用户名完成
  ESC-$       试图进行变量完成
  CTRL-X $    列出所有可能的变量完成
  ESC-@       试图进行主机名完成
  CTRL-X @    列出所有可能的主机名完成
  ESC-!       试图进行命令完成
  CTRL-X !    列出所有可能的命令完成
  ESC-TAB     试图进行历史列表中以前命令的完成
  
  如果你只对长文件名完成感兴趣,最好使用ESC-/而不是TAB。这将确保结果只是文件名,而不是函数或命令名。
**杂项命令
  用几个杂项命令完成对emacs编辑模式的介绍,如表2-6所示。
  
  表2-6     emacs模式杂项命令
  命令      描述
  CTRL-J    等同于RETURN
  CTRL-L    清除屏幕,将当前行放在屏幕最上面
  CTRL-M    等同于RETURN
  CTRL-O    等同于RETURN,随后显示命令历史中下一行
  CTRL-T    颠倒光标左右的两个字符,将光标向前移一个
  CTRL-U    从光标位置开始删除行
  CTRL-V    引用插入
  CTRL-[    等同于ESC(对于大部分键盘)
  ESC-C     光标处单词首字母大写
  ESC-U     光标处单词的字母全部变为大写
  ESC-L     光标处单词的字母全部变为小写
  ESC-.     在光标后插入前面命令行中的最后一个单词
  ESC-_     等同于ESC-.
  
  派生于BSD的系统分别使用CTRL-V和CTRL-W作为“引用下一个字符”和“单词擦除”终端接口功能的默认设置。
  这些杂项命令很值得讨论,尽管它们并不一定是最常用的emacs命令。
  CTRL-O常用于重复已输入的命令序列。回到序列中的第一个命令,按下CTRL-O而不是RETURN,将会执行该命令并显示历史文件中的下一个命令。再次按下CTRL-O输入该命令并调出下一命令,重复该过程直到看到序列中的最后一个命令,然后再次按下RETURN。
  变化大小写的命令中,偶然按下CAPS LOCK键且没有马上注意到变化的结果时,ESC-L命令十分有用。因为所有字母大写的单词在UNIX中并不常用,你可能不常使用ESC-U。
  CTRL-V使得键入的下一字符以本来的面目出现在命令行中。如果它是一个编辑命令(或类似CTRL-D的特殊字符),它的特殊含义将被屏蔽。
  看起来有许多与RETURN意义相同的键,实际上CTRL-M等同于RETURN的ASCII字符,CTRL-J等同于LINEFEED,UNIX常用它来替代RETURN。
  如果要对一个给定文件运行多个命令,就要使用ESC-.和ESC-_。通常UNIX惯例规定文件名总是作为命令的最后一个参数。因此可以通过在每个命令后面输入SPACE,然后再键入ESC-.或ESC-_来省略文件名的键入。例如,要使用more检查一个文件,可以键入:
  $ more myfilewithaverylongname
  然后如果决定要打印它,可以键入打印命令lp。通过键入lp后跟空格,然后键入ESC-.或ESC-_,就可以不用键入长的文件名,bash将自动插入myfilewithaverylongname。
**vi编辑模式
  类似于emacs模式,vi模式也是在历史文件中创建一个单行的编辑窗口。vi模式很流行,因为vi是最标准的UNIX编辑器。vi的设计意图主要是用来编写C程序,它对命令解释器有不同的编辑需求,造成的结果是:虽然可能解决复杂问题时键入较少的键,但对于在bash中实现时相对简单的问题,却可能需要键入很多次。
  像vi一样,vi模式有两种模式:输入模式和控制模式。前者主要用于命令输入(就像通常bash的用法),后者主要用于在命令行和历史文件中移动。输入模式下,可以键入命令,按RETURN运行它们。还可以通过控制字符实现一些简单的编辑功能,如表2-7所示。
  
  表2-7    vi输入模式下的编辑功能
  命令     说明
  DEL      删除前面的字符
  CTRL-W   删除前面的单词(即删除至空格)
  CTRL-V   引用下一字符
  ESC      输入控制模式(见下文)
  
  注意,其中一些命令与UNIX通过终端接口提供的编辑命令是一样的(依赖于UNIX版本)。vi模式使用“擦除”符作为“删除前面字符”的键,通常设置为DEL或CTRL-H(BACKSPACE)。CTRL-V工作方式与在emacs模式中一样,它使得命令行中出现的下一字符不再具备特殊含义。
  在正常环境下,一般使用输入模式。但如果要返回对命令进行改动或重新调出以前的命令,就需要进入控制模式。做到这一点只需按ESC键即可。
**简单的控制模式命令
  所有的vi编辑命令在控制模式下均可用。最简单的是在命令行中进行移动,如表2-8所示。vi模式包含两种“单词”概念。最简单的一种是任意非空(non-blank)字符序列,这里称之为非空单词。另一种是字母数字序列(字母和数字)加上下划线(_)或任意非字母数字序列,这里称之为单词。
  
  表2-8  基本vi控制模式命令
  命令   说明
  h      向左移动一个字符
  l      向右移动一个字符
  w      向右移动一个单词
  b      向左移动一个单词
  W      移到下一个非空字符的开始
  B      移到上一个非空字符的开始
  e      移到当前单词的结尾
  E      移到当前非空单词的结尾
  0      移到行首
  ^      移到行内第一个非空字符
  $      移到行尾
  
  除了最后3个命令之外,所有这些命令前面都可以加上数字表示重复次数。键入重复次数数值时,其数值在重复命令执行过程中替代了命令提示符。如果键盘有光标移动键(“箭头”键),可以使用左右键来代替h和l键在字符间移动。重复次数对光标移动键也起作用。
  习惯于使用正则表达式的UNIX工具(如grep)以及vi的用户一定对最后两条命令很熟悉。
  例如,键入了一行后,在按RETURN前,要改动它:
  $ fgrep -l Duchess < ~cam/book/alice_in_wonderland[[ ]]
  如上所示,光标在行的最后一个字符后。首先点击ESC,进入控制模式;光标会向前移动一个空格,位于d处。然后键入h,则光标向前移动到n。如果从n开始键入3h,则光标将会停止在r上。
  现在看看两种“单词”概念的差别。键入$返回到行尾。如果键入b,则起作用的单词是alice_in_wonderland,光标结束在a:
  $ fgrep -l Duchess < ~cam/book/[[a]]lice_in_wonderland
  如果再次键入b,下个单词是斜线(它是一个非希腊字符的序列),因此光标停在其上:
  $ fgrep -l Duchess < ~cam/book[[/]]alice_in_wonderland
  然而,如果键入B而不是b,非空单词将是整个路径名,光标会停在开始处,即~处:
  $ fgrep -l Duchess < [[~]]cam/book/alice_in_wonderland
  必须键入b4次或键入4b才能达到同样的效果,因为在路径名中/alice_in_wonderland左边有4个“单词”,分别为:book、斜线、cam和前面的~。
  这时,w和W功能相反:键入w光标到达c,因为~是一个单词,而键入W会到达行尾,但w和W都是使光标到达下一单词的开始。e和E使你到达当前单词的结尾,因而,光标在~位置时键入w,会得到:
  $ fgrep -l Duchess < ~[[c]]am/book/alice_in_wonderland
  再键入e得到:
  $ fgrep -l Duchess < ~ca[[m]]/book/alice_in_wonderland
  再键入一个w得到:
  $ fgrep -l Duchess < ~cam[[/]]book/alice_in_wonderland
  另一方面,键入E将到达当前非空单词的结尾——这里即为行尾(如果发现这些命令不好记忆,那就对了,唯一的办法就是多加实践)。
**输入文本和改变文本
  介绍了如何进入控制模式并在命令行内移动之后,下面就要介绍如何返回到输入模式,这样你才可以进行改动,并再键入其他的命令。许多命令使你可以从控制模式进入输入模式,这些进入输入模式的命令各有不同,如表2-9所示。
  
  表2-9  进入vi输入模式的命令
  命令   说明
  i      在当前字符前插入文本(插入)
  a      在当前字符后插入文本(附加)
  I      在行首插入文本
  A      在行尾插入文本
  R      用文本覆盖已存在的文本
  
  最常用的是i或a,偶尔会用到R。I和A分别是0i和$a的简写。为阐述i、a和R之间的区别,请看下面的示例行:
  $ fgrep -l Duchess < ~cam/book[[/]]alice_in_wonderland
  如果键入i后跟end,会得到:
  $ fgrep -l Duchess < ~cam/bookend[[/]]alice_in_wonderland
  也就是说,光标总是出现在alice_in_wonderland前的/下。但如果键入a而不是i,光标会向右移动一个空格。然后如果键入miss_,会得到:
  $ fgrep -l Duchess < ~cam/book/miss_[[a]]lice_in_wonderland
  也就是说,光标总是出现在键入的最后一个字符之后,直到键入ESC停止输入。最后,如果返回到alice_in_wonderland里的第一个a,换而键入R,再键入through_the_looking_glass,会看到:
  $ fgrep -l Duchess < ~cam/book/through_the_looking_glas[[s]]
  换句话说,就是替换(因此是R)而不是插入文本。
  为什么用大写R而不是小写呢?后者是另一个不同的命令,它只替换一个字符,并不进入输入模式。使用r后,输入的字符将覆盖光标所在的字符。因此如果对最开始的命令行键入r,后跟一个分号,会得到:
  $ fgrep -l Duchess < ~cam/book[[;]]alice_in_wonderland
  如果在r前加数字N,则允许你替换该行中光标后N个存在的字符——但仍未进入输入模式。r命令对修正错误的选项字母、I/O重定向字符、拼写错误等很有效。
**删除命令
  介绍了如何输入命令以及在命令行内移动后,下面将介绍如何进行删除。vi模式中基本的删除命令是d后跟一个其他字母。该字母决定删除方向和删除单元,它对应于表2-8中给出的移动命令。
  表2-10显示了一些常用的例子。
  
  表2-10  vi模式删除命令
  命令    说明
  dh      向后删除一个字符
  dl      向前删除一个字符
  db      向后删除一个单词
  dw      向前删除一个单词
  dB      向后删除一个非空单词
  dW      向前删除一个非空单词
  d$      删除到行尾
  d0      删除到行首
  
  这些命令有一些变体和缩写。如果你使用c而不是d,则删除后进入输入模式。在d(或c)前或后可以加上数字重复计数。表2-11列出了可用的缩写。
  
  表2-11  vi模式删除命令缩写
  命令    说明
  D       等价于d$(删除到行尾)
  dd      等价于0d$(删除整行)
  C       等价于c$(删除到行尾,进入输入模式)
  cc      等价于0c$(删除整行,进入输入模式)
  X       等价于dl(向后删除字符)
  x       等价于dh(向前删除字符)
  
  大多数人使用D来删除到行尾,dd删除整行,x(作为“回退”)删除单个字符。如果你不是一个熟练的vi用户,你就会发现很难进行更深入的删除操作命令。
  每个好的编辑器都提供删除恢复命令以及删除命令。vi模式也不例外。vi模式包含一个删除缓冲器,保存对当前行文本的所有改动(注意,这和完全vi模式不同)。命令u恢复对前面文本修改。如果键入u,将会恢复最后的改动,再次键入会恢复以前的改动,当再没有可恢复的操作时,bash响铃。相关命令是.(点号),它重复最后一次文本修改命令。
  还有一种将文本保存到删除缓冲器里而不必先执行删除操作方式:键入删除命令y(yank,移出),而不是d。这样不会改动任何文本,但允许你以后随意检索移出的文本。检索移出文本的命令是p以及P,前者会在当前行光标右边插入文本,后者可在光标左边插入文本。y、p和P命令很强大,更适合“真正vi”任务,如全局改变文档或程序,而不是针对shell命令,因此这里不会经常使用它们。
**在历史文件中移动
  下一组要讨论的vi控制模式命令是在命令历史文件中移动和搜索历史命令。这种功能很重要,它允许你退回去对键入的错误命令进行定位,而不必再次键入整个行。表2-12列出了这些命令。
  
  表2-12   vi控制模式搜索命令历史的命令
  命令     说明
  k或-     向后移动一行
  j或+     向前移动一行
  G        移动到重复计数指定的行
  /string  向后搜索字符串
  ?string  向前搜索字符串
  n        在前一条搜索命令的同一方向上重复进行搜索
  N        在前一条搜索命令的相反方向上重复进行搜索
  
  如果键盘上有箭头键,可用左右箭头光标移动键完成前两个命令。前三个命令前面均可加上重复数字计数(例如,3k或3-,表示在命令历史中向后移动三行)。
  如果你对vi及其文化历史不熟悉,可能会问为什么要分别选择含义不清的h、j、k和l来完成后退字符、向前移动行、向后移动行以及向前移动字符的操作。实际上这种选择很合理,但不是针对标准键盘。Bill Joy最初开发vi是在Lear-Siegler ADM-3a终端上,它曾是第一个定位光标的流行模型(即程序可以发送一个ADM-3a命令,将光标移动到屏幕上的指定位置),ADM-3a的h、j、k和l键在键盘上有小箭头,因此Joy决定在vi中使用这些键来输入适当命令,命令选择的另一合理解释是CTRL-H是传统的退格键,CTRL-J表示退行键。
  也许+和-比j和k更好记,后者的好处是专业打字人员更易使用。这种情况下,有一些历史文件中进行移动操作的最基本命令,下面使用了与前面emacs模式一节中相同的例子来阐述其工作原理。
  输入示例命令(和CTRL-J一样,RETURN与LINEFEED对输入和控制模式均有效):
  $ fgrep -l Duchess < ~cam/book/alice_in_wonderland
  但返回了一个错误信息,指出选项字母错误。将其改变为-s,不需再次键入整个命令。假定在控制模式下(必须键入ESC进入控制模式)、键入k或-将再次得到之前键入的命令,这时光标位于行首:
  $ [[f]]grep -l Duchess < ~cam/book/alice_in_wonderland
  键入w到达-,然后键入l到达l,现在键入rs替换它,按下RETURN运行命令。
  现在得到另一错误信息,只得查阅fgrep的帮助页。你可能记得几天前做过类似事情,因此不必键入整个man命令,只需搜索出最后一次用过的那个。为此,键入ESC进入控制模式(如果已在控制模式下,这将不会起作用),然后键入/后跟man或ma,安全起见,最好键入^ma。^意味着只匹配以ma开始的行。
  但键入/^ma不会得到预期的效果,shell却会给出:
  $ make myprogram
  要再次搜索man,可以键入n,它使用上一搜索字符串做另一次的向后搜索。再键入/并且不带参数,按RETURN,这样实现的功能是一样的。
  G命令检索其编号与提供的数字前缀参数一样的命令,G依赖于第三章“提示变量”一节描述的命令编号策略。如果没有前缀参数,则得到编号为1的命令。这对仍使用命令编号的C shell用户是很有用的。
**字符查找命令
  在vi模式中还有一些移动命令,它们使用的不像我们前面介绍的那些命令那么频繁。这些命令允许你移动到一行的特定字符位置。表2-13列出了这些命令,这里x代表任意字符。
  这些命令前均可加重复计数。
  
  表2-13   vi模式字符串查询命令
  命令     说明
  fx       向右移到x下一次出现的位置
  Fx       向左移到x前一次出现的位置
  tx       向右移到x下一次出现的位置,并后退一格
  Tx       向左移到x前一次出现的位置,并前进一格
  ;        重复上一次字符查询命令
  ,        以相反方向重复上一次字符查询命令
  
  仍以前面的例子开始:要将Duchess改变为Duckess,首先在行尾(或在Duchess中h的右边的任一位置),键入Fh,光标移动到h:
  $ fgrep -l Duc[[h]]ess < ~cam/book/alice_in_wonderland
  这时,键入r以k替代h,但如果要将Duchess改变为Dutchess,则需要向u的右边移动一格。当然,可以只键入l,但假定你在Duchess的右边某处,移到c的最快方法是键入Tu(而不是Fu后跟l)。
  下面介绍如何对字符查询命令使用重复计数。这里要将文件名从alice_in_wonderland改为alice,假定光标仍在D,需要到达第二个斜线处的字符。为此,应键入2fa,然后光标就到了alice_in_wonderland的a处。
  字符查询命令也与删除命令相关,看看前面表格中的命令定义,想像着将移动替换为删除,那就是在字符查询命令前加上d所产生的结果。作为参数给出的字符也将被删除,例如,假定光标在alice_in_wonderland的a处:
  $ fgrep -l Duchess < ~cam/book/[[a]]lice_in_wonderland
  如果要将alice_in_wonderland改变为natalie_in_wonderland,可以键入dfc,意思是“向右删至下一个c处”,即删除“alic”。键入i(进入输入模式),然后键入“natali”来完成这一改变。
  vi控制模式命令中最后一个对当前行进行操作的命令是使用管道字符(|)移动到指定列,其数值由数字前缀参数给定。列计数从1开始,并且只对输入计数,而不计提示字符串占据的空间。默认重复计数是1。当然,键入(|)本身等价于0(见表2-8)。
**文字完成
  虽然字符查询命令和|不是很有用,vi模式还提供了另外一种你经常会用到的特性——文本完成。此特性不是vi编辑器的一部分,它来源于emacs的类似特性,最初应用于DEC大型机的TOPS-20操作系统。
  文字完成的原理很简单:你只需要键入一个文件名、用户名、函数等的部分文字,反斜线命令告诉bash执行vi模式中的完成操作。如果键入一个单词,按ESC进入控制模式,然后键入\,这时将发生下面4个操作中的1种,它们与emacs模式中的TAB键功能一样:
  1.如果没有以该名字开始的文本,shell将响铃并且不做任何事。
  2.如果在搜索路径不存在唯一匹配该字符串的命令名、函数名或文件名,shell就会打印其余部分,后跟空格,以便你键入其他的命令参数。只有在单词位于一行的命令位置时系统才会尝试命令名完成(例如,一行的开始)。
  3.如果存在唯一匹配字符串的一个目录,shell会完成其文件名,后跟一个斜线。
  4.如果有完成名字的多种结果,shell会给出所有候选的最长公共前缀。搜索路径下的命令名和函数名优先于文件名。
  相关命令是*,其功能类似于ESC-\。但如果有多个完成结果匹配(前面列出中的第4种情况),则全部列出,并允许你进一步键入。因而,它类似于shell通配符*。
  =命令很少用到,它也是*功能的扩充,但方式不同。它不将名字扩展到命令行,而是打印它们,然后返回shell提示符,再次键入=前命令行上的内容。例如,如果用户目录下的文件包括tweedledee.c和tweedledum.c,键入tweedl,后跟ESC,然后键入=,结果为:
  $ cc tweedl
  tweedledee.c tweedledum.c
  还可以扩展其他环境实体,这一点在emacs模式中讲过。如果被扩展的文本前面是符号$,shell会试图将其扩展成shell变量名;如果文本前面是符号~,会扩展成用户名;如果前面是符号@,则扩展为主机名。
**杂项命令
  vi模式中有几个杂项命令,如表2-14所示。
  
  表2-14   杂项vi模式命令
  命令     说明
  ~        转换当前字符大小写
  -        扩充前面命令最后一个单词,进入输入模式
  CTRL-L   清除屏幕,在顶部重写当前行;这在屏幕很乱时很有用
  #        给当前行前面加#符号(注释符),并将其发送到历史文件;可用于保存命令以便以后运行,那时就不需要再次键入了
  
  第一个命令前可加重复计数。重复计数n加上符号~将改变下n个字符的大小写,光标相应会往前走。
  重复计数加上_使得前面命令中的第n个单词被插入到当前行;没有计数,则最后一个单词被使用。省略计数也很有用,因为一个文件名通常是UNIX命令行的最后一个参数,用户常常会在一个操作序列中运行几个针对同一个文件的命令。有了此特性,就可以键入所有命令(除了第一个),后跟ESC_,shell将会插入文件名。
**fc命令
  fc是内置的shell命令,提供C shell历史机制的超集。可以使用它来检验最近输入的命令,以便用你喜欢的“实际”编辑器编辑一个或多个命令,并在改变后再次运行那些命令,而无需再次键入整个命令行。下面依次介绍其用法。
  fc的-l选项列出以前的命令。其参数指向历史文件中的命令。参数可以是数字或字母数字字符串。数字指向历史文件中的命令,而字符串指向的是以该字符串开始的最近命令。fc的参数形式相当复杂:
  ·如果给出两个参数,它们作为要显示的第一个命令和最后一个命令。
  ·如果指定一个数字参数,只有带有该编号的命令被显示。
  ·带有一个字符串参数,则其搜索以该字符串开始的最近命令,并显示从此命令到最近命令的所有命令。
  ·如果未指定参数,会看到最后输入的16个命令,bash还有一个内置的命令用来显示历史:history。
  为清楚起见,下面给出几个例子。登录到系统后,输入以下命令:
  ls -l
  more myfile
  vi myfile
  wc -l myfile
  pr myfile | lp -h
  如果键入fc -l,不带参数,会看到上述命令及其编号,如:
  1      ls -l
  2      more myfile
  3      vi myfile
  4      wc -l myfile
  5      pr myfile | lp -h
  加一个选项-n,则隐匿了行编号。如果只想看第2行到第4行的命令,键入fc -l 2 4。如果只想看vi命令,键入fc -l 3.要看vi命令以下的命令,键入fc -l v。最后,如果要看more和wc之间的命令,可以键入fc -l m w, fc -l m 4, fc -l 2 4等。
  对于fc,另一重要选项是-e,表示“编辑”。如果你不习惯vi或emacs模式编辑器,它可用做它们的“应急出口”。可以指定你喜欢的编辑器的路径名,并编辑历史文件中的命令。然后,当改变完成后,shell就会执行新的命令行。
  这里假设你喜欢的编辑器是一个称为zed的自制产品。可以通过键入如下内容编辑命令:
  $ fc -e /usr/local/bin/zed
  如果只为纠正以前输入命令中的错误,这似乎太费事了。很幸运,还有更好的方式。可以设置环境变量FCEDIT为fc要使用的编辑器的路径名。如果在.bash_profile或环境文件中输入一行:
  FCEDIT=/usr/local/bin/zed
  当调用fc时将得到zed,如果未设置FCEDIT,则bash将使用变量EDITOR的设置。如果它也没有被设置,bash默认使用vi。
  fc常用于纠正最近输入的命令。使用时如果不带选项,对参数的处理与前面讨论的fc -l有点不同。
  ·不带参数,fc载入最近命令。
  ·带一个数字参数,fc载入带有该编号的命令。
  ·带一个字符串参数,fc载入带有以该字符串开始的最近输入命令。
  ·fc带有两个参数,参数将指定命令开始和结束的范围。
  记住fc实际上是在编辑命令后运行它们。因此,最后输入的命令可能会很危险。bash在退出编辑器时会执行指定范围内的所有命令。如果已经键入多行结构(类似于第五章的流控制),结果可能更危险。虽然这可能是生成“立即shell程序”的有效方式,但一种更好的方式是将带有同样参数的fc -ln的输出导入到一个文件中,然后再编辑该文件,对其满意后再执行命令:
  $ fc -l cp > lastcommands
  $ vi lastcommands
  $ source lastcommands
  在这种情况下,在退出编辑器时shell不会试图执行该文件。
  这是fc的最后一个选项。fc -s允许你重新执行一个命令。给出参数后,fc将重新执行以给定字符串开始的最后一个命令;不带参数的fc将重新执行前一个命令。-s选项也允许提供一个模式和替换。例如,如果键入了:
  $ cs prog.c
  可以用fc -s cs=cc修正它。也可以结合字符串搜索:fc -s cs=cc cs,最后一次出现的cs会被找到,并以cc替换。
**历史扩展
  如果你是C shell用户,可能对它提供的历史扩展机制很熟悉。bash提供类似的特性集。历史扩展是重新调用和编辑历史列表的原始方式。重新调用命令的方式是使用事件标志符,表2-15给出了完整列表。
  
  表2-15             事件标志符
  命令               说明
  !                  启动历史替换
  !!                 引用最后一个命令
  !n                 引用命令行n
  !-n                引用当前命令行-n
  !string            引用以string开始的最新历史记录
  !?string?          引用包含string的最新历史命令,结尾的?可选
  ^string1^string2   恢复最后的命令,用string2替换string1
  
  目前最常用的命令是!!,在命令行上键入!!将执行最后一个命令。如果你知道特定命令的命令编号,可以使用!n格式,这里n为命令编号。命令编号可从history命令中决定。另外,还可以通过使用!string再次执行以指定字符串开始的最近命令。
  你可能发现表中的最后一项在修改键入错误时有一定用处。例如,键入:
  $ cat through_the_loking_glass | grep Tweedledee > dee.list
  不用向后移动行将loking改变为looking,只需要键入^lok^look即可。这将会将lok改变为look,然后执行修改后命令。
  也可以通过使用单词标志符引用前面命令中的某单词。表2-16列出可用的标志符。注意,单词计数时,bash(像大多数UNIX程序一样)会从0开始计数,而不是从1。
  
  表2-16   单词标志符
  标志符   说明
  0        行中的第0(1)个单词
  n        行中第n个单词
  ^        第1个参数(第2个单词)
  $        行中最后一个参数
  %        匹配最近的?string搜索结果的单词
  x-y      从x到y的单词范围,-y语义与0-y相同
  *        除第0个(即第1个)单词外所有单词,语义与1-$相同。如果行中只有一个单词,则返回空字符串
  x*       语义与x-$相同
  x-       从x到倒数第2个单词之间的单词
  
  单词标志符跟在事件标志符后面,由分号分隔。例如,你可以重复前面的命令,且带不同参数,方法是键入!!:0后跟新的参数。
  事件标志符后也可跟修改符。修改符如果存在,则跟在单词标志符后。表2-17列出所有可用的修改符。
  
  表2-17       修改符
  修改符       说明
  h            删除路径名尾部部分,只保留头
  r            删除.xxx形式的尾部后缀
  e            删除尾部后缀以外的其他元素
  t            删除所有路径名的头部分,只保留尾部
  p            打印修改后命令,但不执行
  q            引用被替换的单词,屏蔽进一步替换
  x            引用被替换的单词,在空格和新行处分隔它们
  s/old/new/   将old替换为new
  
  事件标志符可使用多个修改符,它们之间用分号分隔。
  历史扩展适用于重新快速执行命令,但它已经被本章前面讲过的命令行编辑特性取代。其范围只针对完成操作,我们更希望你能掌握vi或emacs编辑模式提供的技术。
**readline
  bash的命令行编辑接口称为readline。它实际上是一个GNU项目发展而来的软件库,可用于需要基于文本接口的应用程序。它提供的编辑和文本操作特性使得用户更容易输入和编辑文本。因其重要性,它允许对使用它的所有应用程序的键盘输入和定制方法进行标准化。
  readline给出两种默认的编辑模式:vi或emacs。这两种模式均提供在完全编辑器中的编辑命令的子集。在本章前面几节已经介绍了这些命令集,下面介绍如何设置自己的命令集。
  readline给bash加入了比其他shell更大的灵活性,因为它可以通过键捆绑从命令行或是从一个特定文件被定制。还可以设置readline变量。下面将介绍如何使用自己的启动文件设置readline,然后检验如何从命令行中使用键捆绑功能。
**readline启动文件
  默认启动文件称为.inputrc,如果希望定制readline,它必须存在于用户主目录。可以通过设置环境变量INPUTRC改变默认的文件名(细节参见第三章有关环境变量的部分)。
  bash启动时,它读取启动文件(如果存在),其设置开始生效。启动文件只是一个将键名捆绑到macro或readline功能名的行序列。还可以通过在任意行前加入#给文件加上注释。
  对键名可以使用英文名或键转义序列。例如,捆绑CTRL-T为移动到当前行末尾的移动命令,可在.inputrc中加入Control-t: end-of-line。如果要使用键转义序列,可放入"\C-t<">: end-of-line。\C-是Control的转义序列前缀。键序列的好处是你可以为一个动作指定键序列。在我们的例子中,一旦readline读取了该行,键入CTRL-T将使光标移动到行尾。
  前面例子中的end-of-line就是一个readline功能。readline有60多个功能允许你控制从光标移动到修改文本和命令完成(完整列表请参见bash帮助页)。本章介绍的emacs和vi编辑模式命令都有相关的功能。它们允许你定制默认模式或使用用户定制的键序列形成新的模式。
  除了readline功能键,你还可以将一个宏捆绑到键序列。宏是一个简单的击键序列,包含在单引号或双引号内。键入键序列就像输入宏中的键一样。例如,可以将某些文本捆绑到CTRL-T; "\C-t<">: <">Curiouser and curiouser!<">。按CTRL-T就会使得文本Curiouser and curiouser!出现在命令行上。
  如果要在宏或键序列中使用单引号或双引号,要使用一个斜线符号(\)对其进行转义。表2-18列出了常用的转义序列。
  
  表2-18  转义序列
  序列    说明
  \C-     控制键前缀
  \M-     元(转义)键前缀
  \e      转义字符
  \\      斜线字符(\)
  \<">    双引号字符(")
  \'      单引号字符(')
  
  readline还允许在.inputrc中使用简单条件,有3个指令:$if,$else和$endif。$if的条件可为一个编辑模式、终端类型或一个应用指定的条件。
  为测试编辑模式,使用形式mode=并测试vi或emacs。例如,设置readline使CTRL-T只出现在emacs模式中,可以在.inputrc中给出以下内容:
  $if mode=emacs
  "\C-t": "Curiouser and curiouser!"
  $endif
  类似的,要测试一个终端类型,可以使用term=,在测试的右边必须给出完整的终端名。当需要与终端相关的键捆绑时,这很有用。例如,要将功能键捆绑为特定终端的键序列。
  如果有其他使用readline的应用程序。你会希望将bash专有的捆绑分离出来。可以用最后一个条件实现。每个使用readline的应用设置其变量以便你可以对其进行测试。要测试bash,可以将$if bash放在文件.inputrc中。
**readline变量
  readline有其自己的变量集,可以在.inputrc中对其进行设置。表2-19中列出了这些变量。
  
  表2-19    readline变量
  变量                     说明
  bell-style               如果设置为none,readline不会响铃。如果设置为visible,readline试图使用一个可视铃。如果设置为audible,则会响铃。默认为audible。
  comment-begin            执行readline insert-comment命令时插入的字符串。缺省为#。
  completion-query-items   如果完成数目大于给定数目,判断用户何时被要求查看进一步完成情况,默认为100。
  convert-meta             如果设置为On,将第8位为1的字符转换为ASCII键序列,方法是忽略第8位,并在其前面加转义字符,默认为On。
  disable-completion       如果设置为On,禁止单词完成,完成字符被插入到行内,就像它们已被映射到self-insert一样。默认为Off。
  editing-mode             设置vi或emacs编辑模式。
  enable-keypad            如果设置为On,readline试图在被调用时使能键盘的应用小键盘区。某些系统需要它来使能箭头键,默认为Off。
  expand-tilde             如果设置为On,readline进行单词扩展时会使能~扩展。默认为Off。
  horizontal-scroll-mode   设置为On意味着如果键入超出了屏幕的右边界,行会水平滚动。默认为Off,它使得该行自动回车换行。
  input-meta               如果设置为On,则接受8位的输入,默认为Off。其与meta-flag语义相同。
  keymap                   设置readline的当前捆绑键映射。可接受的名称为emacs,emacs-standard,emacs-meta,emacs-ctlx,vi,vi-move,vi-command和vi-insert。默认为emacs。注意editing-mode取值也对键映射有效。
  mark-directories         如果设置为On,则在已完成的目录名后加斜线。
  mark-modified-lines      如果设置为On,在已被修改的历史文件行前加星号。缺省为Off。
  meta-flag                如果设置为On,接受8位的输入,默认为Off。
  output-meta              如果设置为On,则直接显示第8位为1的字符,默认为Off。
  show-all-if-ambiguous    如果设置为On,则具有多个完成可能的单词被列出而不是响铃。默认为Off。
  visible-stats            如果设置为On,表示stat系统调用给出的文件类型的字符在列出所有完成可能情况时附加在文件名后,默认为Off。
  
  要设置任意变量,可以在.inputrc中使用set命令。例如,启动时要设置为vi模式,在.inputrc中加入行set editing-mode vi,这样每次bash启动时就会改变到vi模式。
**使用bind的键捆绑
  如果要进行键捆绑或查看当前设置,可以从bash命令行中使用bind命令完成。捆绑语法与在.inputrc中相同,但必须将每个捆绑加上引号,这样它才会被当做一个参数。
  捆绑一个字符串为CTRL-T,可键入bind '"\C-t": "Curiouser and curiouser!"'。这样就可以将给定字符捆绑到CTRL-T,与.inputrc中一样。不过该捆绑只应用于当前shell,注销以后就会失效。
  bind还允许你打印当前有效的捆绑,方法是键入bind -P。结果如下:
  abort can be found on "\C-g", "\C-x\C-g", "\e\C-g".
  accept-line can be found on "\C-j", "\C-m".
  alias-expand-line is not bound to any keys
  arrow-key-prefix is not bound to any keys
  backward-char can be found on "\C-b", "\eOD", "\e[D".
  ...
  如果只是要查看readline功能名,可以使用bind -l。
  可能用到的另一选项是-p。它以一种bind可重读或可用于.inputrc文件的格式将捆绑打印到标准输出。因此,要创建完整的.inputrc文件,然后再对其进行编辑,可以键入bind -p > .inputrc。
  要再次读取该文件,可使用另一选项-f,此选项将文件名作为其参数,从该文件中读取键捆绑。如果修改了.inputrc文件,可以使用它重新键捆绑。
**键盘习惯
  本章介绍了bash提供的两种命令行编辑模式:vi和emacs。你可能会疑惑为什么选择这两种编辑器。最基本的原因是因为vi和emacs再UNIX中使用最广泛。使用这两种编辑器的人会发现编辑功能都很熟悉。
  如果你不熟悉这些编辑器,就要考虑采纳emacs模式键盘习惯。因为它基于控制键,并不需要考虑“命令模式”和“插入模式”概念。你会发现emacs模式很容易学习。完整的emacs是一个功能很强大的编辑器,其命令结构使其在小子集上也很合适:UNIX、MS-DOS和其他系统都有一些“微型”的emacs编辑器。
  而vi的命令结构实际上对全屏幕编辑器才有意义。vi功能也很强大,但只有当其使用场合与其设计意图相吻合时才能发挥其优势:编辑C或LISP的源码。正如前面提到的,一个vi用户用很少的键就可以实现很强大的功能,但代价却是不能用这些很少的键实现任意事情。而后者才正是一个命令解释器所最需要的,特别是当前用户的大部分时间花在应用程序上,而在shell上只花费很少的时间。简单的说,如果你不熟悉vi,可能会发现它的命令很含糊且混乱。
  两种bash编辑模式都有很多命令,你必须培养出一种包含它们的键盘习惯。如果你使用emacs模式,对完整的emacs并不熟悉,下面列出了几个容易学习的操作,使你可以做任意事情:
  ·对于命令行光标移动,可以使用CTRL-A和CTRL-E移动到行首和行尾,CTRL-F和CTRL-B来进行移动。
  ·删除时使用DEL(意即“擦除”键)和CTRL-D;对于CTRL-F和CTRL-B必要时按住使它们重复,使用CTRL-K擦除整个行。
  ·使用CTRL-P和CTRL-N(或上下箭头键)再命令历史中移动。
  ·使用CTRL-R搜索需要再次运行的命令。
  ·使用TAB进行文件名完成。
  学习了这些键后,你可能对命令行编辑就有了一定的了解。
 

猜你喜欢

转载自blog.csdn.net/chenzhengfeng/article/details/81558719