0%

Vim 实用技巧(03):命令行模式 & 管理多个文件

这篇文章介绍 Vim 的命令行模式,以及如何管理多个文件。

命令行模式

技巧 27:结识 Vim 的命令行模式

Vim 的祖先是 vi,而 vi 开创了区分模式编辑的范例。而 vi 编辑器的祖先可以认为是行编辑器 ex。对于某些基于行的编辑任务来说,Ex 命令仍是最佳工具。

在按下 : 键时,会切换到命令行模式。之后输入一条命令,按回车键后即可执行它。任何时候,都可以通过 Esc 从命令行模式切回到普通模式。

由于历史原因,在命令行模式中执行的命令又称作Ex命令。另外,我们按下 / 调出查找提示符,或者使用 <C-r>= 访问表达式寄存器时,命令行模式也会激活。

  • 可以使用Ex命令读写文件::edit:write
  • 创建标签页 :tabnew,及分割窗口 :split
  • 操作参数列表::prev/:next
  • 及缓冲区列表::bprev/:bnext

事实上 Vim 为几乎所有功能都提供了相应的 Ex 命令(使用 :h ex-cmd-index )。

接下来总结常用的操作缓冲区文本的Ex命令:

  • :[range]delete [x]:删除指定的行到寄存器x
  • :[range]yank [x]:复制指定的行到寄存器x
  • :[line]put [x]:在指定行后黏贴寄存器x的内容
  • :[range]copy {address}:把指定范围的行拷贝到 {address} 所指定的行之下
  • :[range]move {address}:把指定范围的行移动到 {address} 所指定的行之下
  • :[range]join:连接指定范围的行
  • :[range]normal {commands}:第指定范围内的每一行执行普通模式命令
  • :[range]substitue/{pattern}/{string}/[flags]:将指定范围内出现的 pattern 替换为 string
  • :[range]global/{pattern}/[cmd]:对指定范围内匹配{pattern}的所有行,在其上执行Ex命令

在命令行模式中输入和在插入模式中类似。而且有些命令可以通用,例如 <C-w><C-u>等。也可以使用 <C-r>{register} 的方式把任意寄存器的内容插入到命令行中。

虽然在命令行提示符环境下,可以使用的动作命令有限,但是 Vim 的命令行窗口提供了构造复杂命令所需的完整编辑能力。

使用 Ex 命令,有时可以比普通模式更快地完成同样的工作。普通模式命令一般操作当前字符或当前行。而 Ex 命令可以在任意位置执行,这意味无需移动光标,就可以使用 Ex 命令做出修改。所以 Ex 命令影响范围广且距离远。

技巧 28:在一行或多个连续行上执行命令

很多 Ex命令 可以用 [range] 指定要操作的范围,可以用行号、位置标记或者是查找模式来指定范围的开始位置及结束位置。可以使用如下方式指定一个范围:

1
:{start},{end}

其中 {start}{end} 都是地址,可以使用行号作为地址,也可以使用查找模式或者位置标记作为地址。

用行号作为地址:只包含数字的 Ex 命令,Vim 会把该数字解析成一个地址,并把光标移到该数字所指定的行上。并且符号 . 代表当前行,$ 代表文件尾行,% 代表文件所有行。

用高亮选取指定范围:可以使用高亮选取指定一个范围:首先使用相应的 v 命令进行高亮选中,然后按下 :,此时命令行就会预先填充一个范围::'<,'>,它代表当前高亮选区的范围。其中 '< 是代表高亮选区首行的位置标记,而 '> 则代表高亮选区的最后一行

用模式指定范围:Vim 也可以使用模式作为一条 Ex 命令的地址。例如如下命令打印 <html>,</html> 内的所有行

1
:/<html>/,/<\/html>/p

用偏移对地址进行修正:还可以使用偏移对地址进行修正,其一般格式为:

1
2
3
:{address}+n
或者
:{address}-n

如果 n 被省略,那么缺省偏移量为 1。{address} 可以是一个行号、一个位置标记、或是一个查找模式。

小结一下:定义范围的语法非常灵活,可以混合搭配行号、位置标记以及查找模式,也可以对它们加以偏移。如下总结了构建 Ex 命令地址及范围的符号:

  • 0:虚拟行,位于文件第一行上方;
  • 1:文件第一行
  • $:文件最后一行
  • .:光标所在行
  • ‘m:标记为m的行
  • ‘<:高亮选区的起始行
  • ‘>:高亮选区的结束行
  • %:文件所有行

技巧 29:使用 :t:m 命令复制和移动行

:copy 命令(及其简写形式 :t:co)让我们可以把一行或多行从文档的一部分复制到另一部分。而 :move 命令(及其简写形式 :m)可以把一行或多行从文档的一部分移到另一部分。

:copy 命令的格式如下(:h :copy),将指定范围的行复制到 address 所指定的行下方:

1
:[range]copy {address}

普通模式下也有复制命令,但是其会使用寄存器,而 :t 命令不会。因此如果不想覆盖默认寄存器中的当前内容时,可以使用 :t 来复制行。而且在复制距离较远的行时,:t 命令通常更加高效。通常来说,普通模式命令适合在本地进行修改,而 Ex 命令则可以远距离操作(Ex命令影响范围广且距离远)。

:move 命令的格式如下(:h :move),用于将指定范围的行移动到 address 所指定的行下方。

1
:[range]move {address}

技巧 30:在指定范围上执行普通模式命令

如果想在一系列连续行上执行一条普通模式命令,可以使用 :normal 命令。该命令与 . 命令或者宏结合使用时,我们只需花费很少努力就能完成大量重复性任务。

例如,有如下文本,想在每一行末尾添加 ;,按照之前的方法,可以先修改一行,在使用 j 命令调到下一行,再用 . 命令重复修改。但是如果有100该行,我们就需要重复100次该动作。这里借助 normal 命令,可以更简单:

  • 首先使用 A; 命令在第一行添加修改
  • 之后使用 jVG 命令选中之后所有的行
  • 使用 :'<,'>normal . 命令在当前选中的所有行上执行普通命令

当前还有更简单的方法:直接使用 %normal A; 命令就可在所有行末尾添加 ; 了。

而且需要注意,在执行普通模式命令之前,Vim 会把光标移动到该行起始处。所以通过如下命令,我们可以迅速注释掉一个文件中的所有 C 代码:

1
:%normal i//

:normal 命令把具有强大表现力的 Vim 普通模式命令,和具有大范围影响力的 Ex 命令结合起来,这种结合真的是珠联璧合。

技巧 31:重复上次的 Ex 命令

. 命令可以重复上次的普通模式命令,然而如果想重复上次的 Ex 命令的话,可以使用 @:。例如我们使用 :bnext 在缓冲区列表中正向移动,之后我们就可以在普通模式下使用 @: 重复该 Ex 命令,从而在缓冲区之间循环移动。可以使用 Ctrl-o 跳回到跳转列表的上一条记录。

技巧 32:自动补齐 Ex 命令

如同在 shell 中一样,在命令行中也可以使用 Tab 键自动补全命令。而且它会检查命令行上已输入的上下文,然后在构建合适的补全列表。

而且,使用 <Ctrl-d> 可以让 Vim 显示可用的补全列表。另外,不停按Tab键时,会正向遍历补全列表,而如果想要方向遍历补全列表,可以使用 <Shift-Tab>

技巧 33:把当前单词插入到命令行

即使在命令行模式下,Vim 也始终知道光标位于何处以及哪个分割窗口处于活动状态。因此我们可以把活动窗口的当前单词插入到命令行中。在命令行模式下,<Ctrl-r><Ctrl-w> 映射项会复制光标下的单词并把它插入到命令行中。

例如,如果想把如下行中的this替换成that,可以通过如下步骤:

  • 将光标移动到 this,按下 *
  • 使用 cw 命令,将this替换成that
  • 之后直接输入 :%s//<C-r><C-w>/g 进行替换

这里通过 <C-r><C-w> 不用显示输入替换模式,直接将光标下新输入的”that”插入到命令行中了。

这里可以介绍另一种应用场景。打开 vimrc 文件后,如果想查看某一项的帮助,将光标移动到该设置上,然后使用:help <C-r><C-w> 即可。

技巧 34:回溯历史命令

Vim 会记录命令行模式中执行过的命令,并提供两种方式回溯这些命令,用光标回滚之前的命令或者调用命令行窗口查看先前的命令。

在命令行模式下,可以通过光标的 UP 键或 Down 键进行反向或正向命令回溯。如果在命令行模式下已经输入了部分提示,则只会回溯以输入部分作为开头的命令。使用 <Ctrl-p><Ctrl-n> 也可以达到类似效果。

Vim 缺省会记录最后20条命令。可以通过修改 history 选项,修改其保存上限。

Vim 的命令历史不仅是为当前编辑会话记录的,即使在 Vim 重启之后,这些历史记录仍然存在。Vim 不仅记录 Ex 命令的历史,它也会为查找命令单独保存一份历史记录。

命令行模式只适合从头开始构建命令,它却不是一个编辑文本的好地方。此时我们可以使用命令行窗口。输入 q: 命令访问命令行窗口。命令行窗口就像一个常规的 Vim 缓冲区,只不过它的每行内容都对应着命令历史中的一个条目。当按下 <CR> 键时,当前行中的内容就成为 Ex 命令并加以执行(该命令影响的是打开命令行窗口之前的活动窗口)。这使得我们可以使用 Vim 完整的、基于模式的编辑能力来修改历史命令。

需要注意,当命令行窗口出于打开状态时,其始终拥有焦点。所以除非关闭命令行窗口,否则我们无法切换到其它窗口。

如果我们在命令行模式中如输入了一半,此时想借用命令行窗口,可以使用 <Ctrl-f> 来切换到命令行窗口,并且已经输入的部分仍会保留到命令行窗口中。

另外,可以使用 q/ 打开查找命令历史对应的命令行窗口。

技巧 35:运行 Shell 命令

在 Vim 的命令行模式中,在命令前面加上一个感叹号,即可在 shell 下执行该命令。格式如下:

1
!cmd

例如,在 Vim 命令行中,% 代表当前文件名,因此 :!ls -l % 即可查看当前文件的详细信息。另外,Vim 也提供了一组文件名修饰符,可以从当前文件名中提取出文件路径、扩展名等信息(:h filename-modifiers)。

:!cmd 形式适用于执行一次性命令。如果想在 shell 下执行多条命令,可以直接输入 :shell,启动一个交互式 shell。然后用 exit 命令即可退出 shell 并返回 Vim。

另外,借助Bash的作业控制功能,通过 <Ctrl-z> 将当前 Vim 进程挂起,回到 bash 下。然后通过 fg 命令又将 Vim 进程重新恢复到前台运行。

通过 :read !{cmd} 形式执行命令时,会将命令 cmd 的标准输出重定向到缓冲区中。因此,cmd 命令的输出会添加到当前光标之下。

:write !{cmd}形 式执行命令时,会将当前缓冲区重定向到 cmd 的标准输入。因此,可以把当前缓冲区的内容作为 cmd 命令的输入。

当给定了一个范围时,!{cmd} 命令又具有不同的含义。对于命令 :[range] !{cmd},它会把 [range] 范围内的行作为 cmd 命令的标准输入,然后又将 cmd 命令的标准输出覆盖 [range] 内的文本。因此,[range] 内的文本会被 cmd 命令过滤。

例如,对于如下文本,需要按照第二列进行排序,可以通过如下方式进行:

1
2
3
line 2
line 1
line 3
1
:., $ !sort -k2

之后,便得到排序后的文本:

1
2
3
line 1
line 2
line 3

另外,Vim 提供了一种非常快捷的方式设置该命令中的 range。使用 !{motioin} 命令可切换到命令行模式,并且{motion} 所涵盖的范围就会预置在命令行中

管理多个文件

Vim允许在一个编辑会话中编辑多个文件。缓冲区列表记录了一次编辑会话中打开的所有文件。

技巧 36:用缓冲区列表管理打开的文件

了解文件与缓冲区的区别

文件是存储在磁盘上的,而缓冲区则是存在于内存中。当 Vim 打开一个文件时,该文件的内容被读入一个具有相同名字的缓冲区。当我们编辑一个文件时,其实是在编辑该文件在内存中的映像。只有保存修改后,缓冲区中的内容才会写回到文件中。

结识缓冲区列表

当使用 Vim 打开多个文件时,虽然只会显示一个窗口,并且对应第一个文件,但是其他文件也已经被载入后台缓冲区中,可以使用 :ls 命令查看。:ls 命令会列出所有被载入内存中的缓冲区列表。其中,% 符号指明哪个缓冲区在当前窗口可见,而 # 符号则代表轮换文件。 使用 <Ctrl-^> 可以在当前文件和轮换文件间快速切换。

使用缓冲区列表

  • :bfirst:调到缓冲区列表的开头;
  • :blast:调到缓冲区列表的结尾;
  • :bprev:在列表中方向移动;
  • :bnext:在列表中正向移动;

另外,可以使用 :buffer n 命令快速跳转到编号为 n 的缓冲区中,n 为 Vim 为缓冲区分配的编号,可以通过 :ls 命令查看。或者使用 :buffer {bufname} 的形式进行跳转。

:bufdo 命令允许我们在 ls 命令列出的所有缓冲区上执行 Ex 命令。

删除缓冲区

可以使用 :bdelete 命令删除一个缓冲区,命令格式如下:

  • :bdelete N1 N2 N3:删除编号为 N1、N2、N3 的缓冲区
  • :N, M bdelete:删除编号为 N~M 的缓冲区

注意,删除该缓冲区并不影响缓冲区所关联的文件,仅仅是把该文件在内存中的映像删除掉。

技巧 37:用参数列表将缓冲区分组

参数列表易于管理,适用于对一批文件进行分组,使其更容易访问。

使用 :args 命令可以查看参数列表。参数列表记录了启动时作为参数传递给 Vim 的文件列表。但是我们可以随时改变参数列表的内容。 :args 命令的使用格式如下:

1
:args {arglist}

当不带参数时,打印当前参数列表的内容,否则重新设置参数列表的内容。{arglist} 可以包含文件名,通配符、甚至是一条shell命令的输出结果:

  • 用文件名指定文件::args index.html app.js
  • 使用 Glob 模式指定文件名:* 符号匹配 0 个或多个字符,但它的范围仅限于指定的目录,而不会递归其子目录。** 符号也匹配 0 个或多个字符,但是它可以递归进入指定目录的子目录
  • 用反引号结构指定文件:vim可以执行反引用(``)括起来的命令,并将其输出作为:args命令的参数。

参数列表比缓冲区列表更容易管理,这使得其成为对缓冲区进行分组管理的理想方式。因为我们可以通过 :args {arglist} 命令一下即可清空并重新设置参数列表。接下来可以使用 :next:prev 命令遍历参数列表中的文件。使用 :argdo 命令对在列表中的每个缓冲区执行 ex 命令。

技巧 38:管理隐藏缓冲区

Vim 会对被修改过的缓冲区给予特殊对待,以防未加保存就意味退出。当修改缓冲区后,但没有保存修改,使用 :ls 命令查看缓冲区时,此时该缓冲区前面会多一个+号,代表该缓冲区被修改过了,但还未保存。

1
2
3
4
:ls
1 h "file1" line 1
3 %a + "file" line 2
4 #h "file2" line 1

如果此时使用 bnext 等命令切换到下一个缓冲区,则会给出告警,说当前缓冲区中有未保存的改动。如果需要强行切换,则在命令后面加上 !。如果强制切换成功,而原来未保存的缓冲区前则多了一个 h 标记,代表它是一个隐藏缓冲区。

隐藏缓冲区没有任何不同,但是当退出编辑会话时,Vim 会把隐藏缓冲区载入当前窗口(如果有多个,逐个载入),可以使用 :w 命令进行保存,也可以使用 :edit! 命令,重新从磁盘读取该文件(放弃当前修改)。如果想退出 Vim 并不对未保存的修改进行检查,可以使用 :qall!,如果想直接保存所有缓冲区而无需逐个检查,可以使用 :wall 命令。

考虑一种情况,如果需要对所有缓冲区执行一条 ex 命令,可以使用 :argdo:bufdo。但是该命令的执行过程是逐个遍历缓冲区,并执行 ex 命令。如果该命令需要修改缓冲区时,当修改完一个缓冲区后,由于未保存该缓冲区,Vim 不允许切换到下一个缓冲区。

此时,可以设置 hidden 选项,这样当从一个未保存的缓冲区中切换出去时,Vim 会自动将其为隐藏。这使得:argdo:bufdo 命令可以修改一组缓冲区。最后再使用 :argdo write 或者 :wall 命令来保存所有缓冲区。

技巧 39:将工作区划分成窗口

在 Vim 中,窗口是缓冲区的显示区域。Vim 允许我们将工作区划分成若干窗口,在这些窗口里并排显示多个缓冲区。

创建分割窗口:

  • 使用 <Ctrl-w>s 命令:水平切分当前窗口,新窗口仍显示当前缓冲区;
  • 使用 <Ctrl-w>v 命令:垂直切分当前窗口,新窗口仍显示当前缓冲区;
  • 使用 :edit 命令将另一个缓冲区载入当前窗口。

或者直接通过如下命令:

  • :split {file}:水平切分当前窗口,并在新窗口中载入 {file}
  • :vsplit {file}:垂直切分当前窗口,并在新窗口中载入{file};

在窗口间切换

Vim 提供了一些在窗口间切换的命令:

  • <Ctrl-w>w:在窗口间循环切换
  • <Ctrl-w><Ctrl-w>:在窗口间循环切换
  • <Ctrl-w>h:切换到左边的窗口
  • <Ctrl-w>j:切换到下边的窗口
  • <Ctrl-w>k:切换到上边的窗口
  • <Ctrl-w>l:切换到右边的窗口

如果你的终端支持鼠标操作,或者使用 Gvim,可以通过鼠标点击来激活一个窗口,前提是正确激活了 mouse 选项。

关闭窗口

Ex 命令 普通模式命令 用途
:close <Ctrl-w>c 关闭活动窗口
:only <Ctrl-w>o 只保留活动窗口,关闭其它所有窗口

改变窗口大小及重新排列窗口

Vim 提供了一些用于改变窗口大小的按键映射项,完整列表可以通过 :h window-resize 查看。常用的命令如下:

  • <Ctrl-w>=:使所有窗口等宽、等高
  • <Ctrl-w>_:最大化窗口的高度
  • <Ctrl-w>|:最大化窗口的宽度
  • [N]<Ctrl-w>_:把活动窗口的高度设置为 N 行
  • [N]<Ctrl-w>|:把活动窗口的宽度设置为 N 列

关于重排窗口的命令,可以使用 :h window-moving 了解更多细节。

技巧 40:用标签页将窗口分组

在 Vim 里,标签页是可以容纳一系列窗口的容器(参考 :h tabpage),标签页与缓冲区并非一一对应的关系,相反,标签页是容纳一系列窗口的容器

如何使用标签页

用 Vim 的标签页可以把工作分割到不同的工作区。如果我们想切换工作内容,同时又不想在当前标签页中打开新文件,避免当前工作区混乱,可以创建一个新的标签页。

使用 :lcd {path} 命令可以设置的当前窗口的本地工作目录。注意,lcd命令只影响当前窗口,而非当前标签页,如果想设置当前标签页的每个工作窗口,可以使用 :windo lcd {path} 命令。

打开及关闭标签页

:tabedit {filename} 命令可以打开新的标签页,如果省略 {filename} 参数,则里面包含空的缓冲区

  • <Ctrl-w>T:把当前窗口移到一个新的标签页;
  • :tabclose:关闭当前标签页及其中的所有窗口;
  • :tabonly:只保留活动标签页,关闭所有其它标签页;

在标签页间切换

标签页编号从1开始,如下命令可以在标签页之间切换:

Ex命令 普通模式命令 用途
:tabnext {N} {N}gt 切换到编号为 {N} 的标签页
:tabnext gt 切换到下一标签页
:tabprevious gT 切换到上一标签页

重排标签页

:tabmove {N}:重排标签页,将当前标签页移动到指定位置,如果N为0,则移动到开头,如果省略 N,则移动到结尾。