这篇文章介绍 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 | :{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 | line 2 |
1 | :., $ !sort -k2 |
之后,便得到排序后的文本:
1 | line 1 |
另外,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 | :ls |
如果此时使用 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,则移动到结尾。