0%

学习 vi 和 vim 编辑器(5):高级编辑方法

这一篇文章将介绍一些 vi 和 ex 编辑器的高级功能,主要内容包括如何自定义编辑环境、如何在 vi 中执行 UNIX 命令、如何保存命令,@ 功能、如何使用 ex 脚本、ctags 工具的使用等。

自定义vi

vi 编辑器中有许多可以设置的选项,这些选项将影响 vi 编辑器的行为。使用 ex 命令 :set 在 vi 中改变配置选项。可以在启动 vi 后,手动使用 :set 命令设置 vi,也可以将 :set 命令写入 .exrc 文件,从而自动配置 vi 环境。

:set 命令

:set 命令可以改变两种类型的选项:

  • 切换选项:只能选择开启与关闭
  • 值选项:可接受数值或字符串值

开启某个切换选项,使用 :set option 命令形式,关闭某个切换选项,使用 :set nooption 命令形式。而对于值选项,使用 set option=value 的形式进行设置。

如下命令可以查看 vi 选项的当前值:

  • :set all:显示完整的选项列表,包括 vi 的默认选项和用户手动设置的选项
  • :set:显示特别设置过的选项
  • :set option?:查看某个选项的当前值

.exrc 文件

vi 编辑器启动时,首先尝试读取位于你 home 目录的 .exrc 文件,以取得 vi 的相关设置。在该文件中,可以使用 :set 命令设置 vi 的各个选项,以配置 vi 环境。这个文件实际上是由 ex 读入,所以在 .exrc 文件中输入的命令前可以不用加冒号。

vi 除了读取 home 目录中的 .exrc 文件,之后还会读取位于当前目录中的 .exrc 文件,这样就可以针对不同的项目配置不同的 vi 选项。但是在 vi 读取当前目录中的 .exrc 文件之前,需要首先在你的主目录的 .exrc 文件中设置 exrc 选项,即 :set exrc。

另外,任何存储在环境变量 EXINIT 中的命令都会在 vi 启动时执行。EXINIT 中的设置比位于 home 目录中的 .exrc 文件先执行。

也可以将设置命令保存到 .exrc 以外的任意文件,然后使用 :source 命令读入该文件即可。

常用选项

vi 编辑器中可以设置非常多的选项,其中大部分是给 vi 内部使用的,通常不需要修改。这里介绍一些常用的选项:

  • number:显示行号
  • list:将 tab键显示为 ^I,行结尾显示为 $ 等等
  • wrapmargin:用于指定右边界的距离,当输入的一行长度超过该限制时,便可自动换行
  • autoindent:自动缩进
  • showmatch:括号的匹配
  • tabstop:设置 tab 键占用的空格数
  • noignorecase:控制执行搜索时是否忽略大小写
  • wrapscan:控制搜索时是否绕回文件头或文件尾继续搜索
  • magic:控制搜索时是否辨识通配符
  • autowrite:设置该选项后,当使用 :n 命令来切换到下一个文件,或者使 :!command 来执行 shell 命令时,vi 将自动把更改过的缓冲区写入磁盘

执行 UNIX 命令

在使用 vi 编辑器时,可以查看或读入任何 UNIX 命令所产生的输出。感叹号 ! 会告诉 ex 创建一个 shell,并将后续的文本视为 UNIX 命令,即通过 :!command 可以在 vi 中执行一个 UNIX 命令。

如果想要执行一系列命令而中途不回到 vi,则可以使用 :!sh 来启动一个交互式 shell,之后通过 CTRL+D 来结束 shell 并回到 vi。

:read 命令可以与 UNIX 命令结合使用,用于将 UNIX 命令的输出结果读入到当前文件中。在 :read 命令前加上行地址,可以指定结果的插入位置,默认是插入到当前行的下一行。

通过命令过滤文本

vi 缓冲区中的文本块也可以作为 UNIX 命令的标准输入,命令的输出则替换了缓冲区中原来的文本。因此可以在 vi 或 ex 中通过 UNIX 命令来过滤文本。在 vi 中要实现文本过滤,可以通过在代表任何文本块的移动命令前加上感叹号,之后再接要执行的 UNIX 命令。而在 ex 中,命令形式则为 :TextObject ! Command

举个例子,在不离开 vi 的情况下,对如下文本进行排序:

通过如下 ex 命令即可实现按数字大小排序:

1
:1,$ ! sort -n

命令执行结果如下:

保存命令

vi 和 ex 中提供了多种方法来保存一长串命令序列,这样当以后需要调用已保存的序列时,只需输入几个字符即可。

定义缩写

通过定义缩写,可以让处于插入模式的 vi 自动替你将缩写输入展开成原文。定义缩写的命令为::ab abbr phrase,其中 abbr 就是对 phrase 的缩写。需要注意,在插入模式中只有将缩写当成单个单词输入时,才会进行展开,单词内的 abbr 不会被展开。:unab abbr 用来取消一个缩写的定义。:ab 命令列出当前定义的所有缩写。

还有一点值得说明,在实际测试时发现,如果缩写出现在所代表的词组中,该缩写只会展开一次,并不会无限递归展开(但有的版本的 vi 编辑器可能会无限展开或者定义缩写失败)。

使用 map 命令

map 命令用于把命令序列映射到一个没有用到的键,所以 map 命令的作用类似于 ab 命令,可以认为 map 命令是对 vi 的命令模式定义宏,而 ab 命令是对插入模式定义宏。

  • :map x sequence:定义字符 x 映射到 sequence 命令序列
  • :unmap x sequence:取消字符x所映射的命令
  • :map:列出所有的映射键

需要注意的是,最好只对命令模式中没有用到的键进行映射,如果 vi 中该键已经有相应的功能,再对该键进行映射,则该键原有的功能会消失。而在命令模式中没有用到的按键有(不同版本的vi会有所不同):

  • 字母键:g、K、q
  • 控制键:^A、^K、^O、^W、^X
  • 符号:_、*、\、=

举个例子,如下命令将交换两个单词顺序的命令序列映射为 q 键(键入如下命令时需要输入两次 enter 键):

1
:map q dwelp

解释一下,dw 用于删除一个单词,e 移到下一个单词的结尾,l 往右移动一格,p 放置刚刚删除的单词。之后在该编辑会话中,就可以使用 q 键来交换两个单词的顺序。

保护按键免被 ex 解释

在定义映射命令时,某些按键并不能直接输入并把它们作为命令序列的一部分,例如 ENTER、ESC、BACKSPACE、DELETE 键等等。如果需要把这些键作为命令序列的一部分,需要在前面加上Ctrl+V,从而转换按键的正常含义。输入 Ctrl+V 后,屏幕上显示的是 ^ 字符,接着输入ENTER 键后,屏幕上显示的是 ^M 。

这种用法可以用在任何 ex 命令中,而不仅仅是 map 命令。这就意味着我们可以在缩写命令或替换命令中输入换行符、退格符等。

注意事项

  • 由于竖线 | 在 ex 命令中具有特殊含义(作为多个 ex 命令的分隔符),所以不要在映射序列中使用竖线
  • 映射序列中可以保包含其他映射命令,也就是说允许嵌套映射序列。该功能是由 vi 编辑器的 remap 选项控制的,该选项默认开启

映射键的范例

  • 范例 1
    很多时候我们用 e 键将光标移动到单词结尾,是为了在单词后面添加内容,因此可以进行如下映射:
1
:map e ea

之后输入 e 键就可以直接移动到单词结尾并进入插入模式了。

  • 范例 2
    保存一个文件并直接编辑下一个文件:
1
:map q :w^M:n^M

再次提醒,屏幕上显示的 ^M 对应的按键为:先输入 Ctrl+V,再输入 ENTER。

  • 范例 3

将C/C++里的某行代码进行注释

1
:map q I//^[

这里的 ^[ 对应的按键为:先输入 Ctrl+V,再输入 ESC 键。

插入模式中的映射键

一般来说,映射应该只在命令模式中有用,因为在插入模式中输入某个按键就应该代表输入相应的文本,而不是代表执行相应的命令序列。但是,通过在 map 命令后面加上感叹号 !,即可强制覆盖按键的原有含义,以产生插入模式中的映射行为。但这样也产生了一个问题:再也无法输入该按键对应的文本。

可以使用 :unmap! 取消插入模式下的映射键。

@ 功能

命名缓冲区提供了另一种方法来创建宏,可将复杂的命令序列保存到某个命名缓冲区中,再通过 @ 命令执行缓冲区中的内容。

举个例子,要在每一行行首添加单词 “begin: ”,可以在插入模式中任起一行,输入如下内容

1
Ibegin:

然后将光标移动到该行行首,执行如下命令,这样该行内容就被删除并被保存到 a 这个命名缓冲区中:

1
"ad$

之后将光标移动到任意一行,执行 @a ,即可将缓冲区中的内容作为命令进行执行。在这个例子中,就是在行首添加单词 “begin: ”。

由于 @ 命令会被解释为 vi 命令,所以不管缓冲区中存储的是什么命令,都可以使用点号 . 来重复这个命令。另外,命令 @@ 会重复上一个 @ 操作。

ex脚本:

上面讲过,vi 启动时会自动读取 home 目录中的 .exrc 文件,因此可以将任意 ex 命令存储在该 .exrc 文件中。这样 vi 启动时,这些 ex 命令将会自动执行。也可以将 ex 命令保存在其他文件中,然后在启动 vi 后,通过 :source scriptname 读入该脚本文件,从而执行该脚本。

另外,在 shell 中也可以直接把 ex 脚本作用在某个文件上,这里需要用到 shell 的重定向功能。如下命令,将名为 scriptname 的 ex 脚本作用于 filename 文件。

1
ex - filename < scriptname

在 shell 脚本中,可以包含对 ex 脚本调用,通过这种方式,我们可以对文件做更灵活的修改。

ex 脚本实例

这里展示一个非常简单的 ex 脚本。如下所示,该 ex 脚本用于删除所有文件内容:

如下 shell 脚本则调用 ex 脚本,清空所有传入的文件:

除了 ex 编辑器,UNIX 还提供了比 ex 更具有威力的编辑器:sed 流编辑器与 awk 数据操纵语言,这些工具提供了更加强大的编辑功能。

编程相关

vi 编辑器为程序员们提供了非常多强大的功能,包括缩排控制、利用 % 进行括号匹配、ctags 的使用等。

缩排控制:

vi 提供了自动缩排控制,如果需要使用 vi 中的自动缩排功能,需要通过如下命令进行设置::set autoindent。这样当使用空格或 tab 键做缩排时,后面的行会自动以相同的距离做缩排。当你在自动缩排的情况下输入代码时,在一行的开头按下 Ctrl + T 会进入到下一个缩排层级,而按下 Ctrl + D 则可回到上一个缩排的层级。需要注意的是,Ctrl + T 和 Ctrl + D 是在插入模式中输入的。

<< 与 >> 命令也可用于对代码进行缩排。>> 默认会将代码往右移动 8 个空格,而 << 默认将代码往左移动8个空格。默认移动的空格数可以通过 shitwidth 选项进行设置。可以同时移动多行,只需要在 << 或 >> 前面加上数值即可。

一个特殊的搜索命令

(、[、{、< 都可以称为开括号,当光标位于任何一种开括号上时,按下 % 键,可以将光标移动到成对的闭括号)、]、}、> 上。同样地,当光标位于某一个闭括号上时,按下 % 键可以回到相应的开括号上。

当光标不是位于括号字符上时,按下 % 键后,vi 会在当前这一行寻找第一个开或闭括号,再将光标移动到与之相对应的括号上。

使用标签(tag):

UNIX 上的 ctags 命令可以产生一个信息文件,vi 编辑器可以用该文件判断各个源文件中分别定义了哪些函数等。默认情况下,这个文件名 tags。

将 UNIX 中的 ctags 命令和 vi 中的 :tag 命令结合起来使用,可以快速移动到某个标签的位置。首先在 UNIX 命令行中用 ctags 为程序源文件创建相应的 tags 文件,然后在 vi 编辑器中使用 :tag name 命令将光标移动到 name 所在的行。

如果查找的标签位于新的文件中,但是当前文件还没有保存,vi 不会让你读入新的文件,此时必须先用 :w 命令保存当前文件,或者也可以通过 :tag! name 来强制使 vi 放弃编辑结果。