《Practical Vim》笔记及总结

最近集中读了几本关于Vim的书,�以《Practical Vim》最为详尽,特重新温习并总结成篇,一来作为复习和练习,二来方便今后备查。

由于主要给自己看,一些特别基本的操作就不在赘述,了解normal mode和insert mode的区别和基本操作,能够进行基本的增删改操作即可。

本文示例摘抄翻译自Drew Neail的《Practical Vim》一书

<C-v>代表Ctrl+v,这里的例子主要适用于linux环境下的vim,对于windows环境,存在快捷命令冲突的情况,需要注意。

主要内容:

  1. Vim的Visual Mode和Command-Line Mode
  2. 文件中、文件之间快速跳转的技巧
  3. Vim的寄存器、宏命令
  4. Vim的模式匹配、搜索与替换--特别是神奇的global命令

I Visual Mode

可视模式,用于选择“一块”内容,并进行相应操作。

命令 作用
v 基于单词的可视模式
V 基于行的可视模式
<C-v> 基于块的可视模式
gv 重新选中上次选中的块
o命令

在进入可是模式后,相关的移动操作依然可用,在移动的同时会选中经过的内容,这里有一个比较有用的移动命令是o,用于跳转到本次选中区域的另一端——就不用取消重选了。

图1-1 o命令的选择演示
用“.”来重复命令

当我们使用.命令来重复之前的执行时,如果是针对一个visual block的执行,重复执行也是再次针对这个block的。
当设置过 :set shiftwidth=4 softtabstop=4 expandtab
这条命令之后,就可以使用>和<命令来进行缩进和取消缩进的操作了。这时候,通过v命令选中后,执行>命令缩进。此时如果要再缩进一级,直接使用.命令进行重复就可以了。

图1-2 使用.命令重复执行
vitU和gUit

有时候我们需要修改一段html代码标签内的东西,强大的Vim给了我们一个直接选中标签内内容的命令vit,这个命令是v和it命令的组合,其中it命令是一个特殊的motion command。选中后可以执行各类操作命令。比如如下例子:

图1-3 使用vit命令选中标签内的内容并进行修改

这样的操作有一个缺点,由于vit和U命令是分开的,如果我们使用.命令,它并不是完整的重复vit和U命令,而是针对同一行同样的选中位置部分执行U命令,在上例中,通过j.重复后,在第三行,标签内的内容会变成THRee而不是我们希望的THREE。为了解决这个问题,我们需要将命令在一条内执行完成。
Vim也提供了相应的方案,使用gUit命令就可以,其中gU是一个操作命令,it是确定命令的执行范围。

图1-4 使用gUit命令配合j.批量操作
使用<C-v>来进行列操作

还记得没用Vim之前,第一次进行列操作用的是UltraEdit,当时觉得好酷炫,鼠标拖一下就可以方便的在一列上进行修改;如今连鼠标都不用,按几下键盘就实现了相同的功能,岂不是更爽!


图1-5 使用<C-v>进行列操作

这里面用到的几个技巧:

  • 进入Visual Mode后,通过3j,快速选择四行
  • 注意使用x后,就会退出Visual Mode,需要用.命令来删掉各列的空格
  • 使用gv命令,选择之前选中的列block进行进一步操作
  • 使用yyp复制一行后,用Vr命令全部替换成-字符
批量修改各行文字内容

在使用Visual Mode选中一批内容后,可以进行批量修改,需要注意的是,使用诸如c/I/A命令对选中区域进行修改后(注意这里的插入不使用i/a,是因为i和a已经用于指示对text object的操作了,类似via),显示的只是其中一行的变化,只有当你按下ESC回到Normal Mode的时候,其它行的修改才会呈现出来。

图1-6 批量修改文字内容

II Command Mode

我们可以在Normal Mode下输入":"来进入命令模式,在命令模式下,可以针对一个区域的内容进行增、改、删、替换等等操作,下图比较完整地总结了各项命令模式下可执行的操作:

图2-1 命令模式下的各种操作
选取执行命令的范围

对于以下文档,通过各种选择[range]的方式可以选取到我们想要的行:

<!DOCTYPE html>
<html>
<head><title>Practical Vim</title></head>
<body><h1>Practical Vim</h1></body>
</html>

:/<html>/+1,/</html>/-1p

<head><title>Practical Vim</title></head>
<body><h1>Practical Vim</h1></body>

这里选中的逻辑是,先匹配<html>然后行数加1,再匹配到</html>的行数减1,生成选中范围,然后打印出来。

图2-2 选择range时的各种范围参数
m和t命令

m是move的缩写,t是copy的缩写(或者用co),为什么不是c呢,�嗯..我也不知道,在normal mode下c命令是剪切、修改的命令。
这两个命令还是挺简单的,结合上文关于range的选择,看两个例子就明白了。

图2-3 t命令

m命令和t命令用法一致,只不过t是复制,m是移动,就这点不同。

在Command Mode执行Normal Mode Command

在命令模式下,也可以执行正常模式下的命令,比如.命令,i命令,A命令等等,这样可以方便的在一个范围内执行一个统一的操作。比如注释一批命令行,或者统一在行尾添加分号等。

图2-4 在一行结尾添加;
图2-5 使用V模式配合normal .

以上操作其实可以用一条很简单的外部命令来解决:
:%normal A;

重复执行ex command

在normal mode下,我们使用.命令来重复执行上一条修改。而如果要重复执行上一条ex command,则需要使用@:命令。
当执行过一次@:命令后,我们可以使用@@来继续重复执行。
例如对于上例,我们使用:%normal A;在所有行后添加了;之后,使用@:就可以在所有行后再添加一个;了。

补全ex command

在我们输入ex command的时候,输入命令的一部分后,使用<C-d>可以打印出所有可能的命令列表。

图2-6 补全命令列表

使用<Tab>可以在可能的命令中进行轮询,<S-Tab>进行反向轮询(轮询开始后用左右键也可以)

将当前光标下的内容插入到命令中

<C-r><C-w>这一映射组合可以将光标所在位置的内容插入到命令中去。我们也可以利用*****命令来实现快速的查询匹配。
命令相当于执行/<<C-r><C-w>><CR>(这里< >是一个查询边界,在后面介绍查询相关内容的时候会再具体介绍)
例子:使用
命令进行快速匹配,再配合cw和%s及<C-r><C-w>进行全文替换

图2-7 匹配及替换

再使用:%s//<C-r><C-w>/g就可以把全文的tally替换为counter,这里的匹配中使用//即是使用现有的匹配。

翻找历史命令

输入:后,通过上/下键可以翻找之前执行过的命令。
Vim默认会保存最近20条执行过的命令,可以通过:set history命令来设定一共保存多少条。

Command-Line Window

在进入命令行模式的时候,我们没法方便地编辑、组合多条命令。
这时候我们可以使用q:进入命令行窗口,就可以像编辑文本一样来编辑命令行。如果要推出窗口模式,我们可以选择使用:q(就像退出一个Vim窗口一样),或者我们使用<CR>。要注意的是,如果我们用<CR>来退出命令模式,那当前光标所在行的命令会被执行到活动窗口的文本上。
在Command-Line模式下,我们也可以使用<C-f>来进入Command-Line Window。

图2-8 进入命令行窗口的各种方式

在Shell中执行命令

我们在Vim中,如果想要执行Shell命令,可以很方便地通过:!{cmd}
来进行执行,如果直接执行:shell
命令,可以直接调用出一个shell窗口。
我们可以很方便的将Vim和Shell中的内容分别作为各自的标准输入输出来使用,总结见下表
补充图片补充图片补充图片补充图片补充图片补充图片补充图片
例如我们可以通过:read !ls /很方便地将/目录下的内容读取并输入到当前Vim文档中。
或者,假设我们的光标停在print "hello world"这样一个python语句上,使用:.write !python命令,可以直接调用python执行这段语句。

III Vim的buffer及标签

批量打开文件的buffer

如果我们在命令行下,通过类似vim *.txt的命令打开了一批文件,Vim会把它们读入buffer中去,我们可以通过:bnext :blast等命令依次查看buffer中的文件内容。用:ls就能查看当前的文件状态。这个命令结果中,每个文件都被赋予了一个编号。我们可以通过:buffer N直接跳转到这个文件。
如果我们不想继续编辑了,那就可以通过:bdelete N1 N2 N3..来删除不需要的buffer,也可以通过:N,M bdelete来连续地删除。
Vim内建的操作buffer的工具不是很方便,如果我们想要批量管理文件、批量执行命令,可以通过使用tab list、分屏显示、或者argument list来进行。

argument list

argument list组合一批文件,便于进行浏览,同时也可以批量对他们执行Ex命令。
执行*vim .txt之后,可以使用args来管理批量打开的文件。

图3-1 用args批量管理文件

也可以先进入Vim后,使用args命令打开相应的文件。

$cd code/files/mvc
$vim
:args  index.html app.js
:args

这样也可以达到同样的效果。

也可以使用通配符打开文件:

图3-2 args使用通配符打开文件

使用:args `cat .chapters`命令,还可以从chapters文件里读取内容,作为args命令的输入参数,加载到args list里。
这里我们要注意的是,arg list和buffer list并不是一回事,这其中应该是一个包含的关系,buffer list里是所有当前打开编辑的文件,就像我们整个工作桌面,args命令就像从中单独拉出一批文件放置在一堆,便于我们查看和批量操作。
使用:next :prev来遍历arg list中的文件。

hidden模式与argdo/bufdo批量操作

使用:h hidden我们可以看到hidden buffer的作用。
默认情况下,hidden是关闭的,意味着当一个buffer被舍弃时就被unload了;当hidden开启的情况下,当一个buffer被舍弃,只是被隐藏起来。
当我们编辑了一个buffer后,没有保存就使用:bnext进入下一个buffer时,Vim会提示我们需要确认是否保存修改,如果我们不想理会这一提示,可以使用:bnext!,这样之前的buffer就进入了hidden状态。
当我们退出Vim的时候,Vim会将hidden模式的buffer按次序调用到活动窗口,询问我们是否需要保存修改。

图3-3 关于hidden buffer的相关选项

在使用:argdo和:bufdo命令的时候,Vim执行的是如下的顺序

:first
:{cmd}
:next
:{cmd}

这样对于被修改过的前一个buffer,Vim就会不停询问是否要处理修改结果。这时候我们就应该将hidden选项置为on,一口气执行完命令,然后:wa保存所有的改变。

分隔窗口

Vim可以将窗口分隔成多个区块,进行管理。分割后,多个分隔窗口共享同一个buffer,展示同一个文件。使用:e命令可以在新分隔的窗口中打开新的内容。使用:sp {file}也能达到同样的效果。


图3-4 分隔窗口
图3-5 关闭窗口
图3-6 重新规划窗口大小
使用标签浏览

当我们用:e在Vim中编辑一个文件,默认情况下Vim会把它加入到buffer,然后在当前的窗口中展示它。为了展示方便,我们也可以使用Tab标签来管理文件。
要注意的是,Vim的标签和其它一些文本编辑器的标签不一样。一般文本编辑器的一个标签,就代表了一个打开的文件,但Vim的标签和buffer内容并不是一个一对一的关系,Vim的每个标签都可以看成一组窗口的集合,从下图可以看出,每个标签对应的窗口集合,深色是活动窗口。

图3-7 Vim的标签用于窗口集合的管理

下表中列出了标签的相关管理命令,包括打开关闭和切换:


图3-8 标签打开及关闭
�图3-9 标签切换

如果要改变标签的顺序,我们可以使用:tabmove [N]这个Ex命令来实现。

IV Vim的文件打开及保存

在:edit中列出路径

使用:edit {file}打开一个文件进行编辑,是Vim基本的编辑方式。
在打开保存中,有时候需要修改保存的路径,这时候要是能快速查看到当前编辑文件的路径会很方便。
按《Practical Vim》所说,用:edit %<Tab>这里的%表示了当前文件的绝对路径,通过Tab就可以补全出完整内容。
但实际操作过程中,我在Vim中使用这个方法显示的是相对路径,经过查询资料,使用:edit %:p<Tab>命令,实现了绝对路径的效果。不知道是版本差异还是配置原因,用的时候注意一下就好。
一个更有用的技巧,是使用:h来去掉文件名,只列出绝对路径的目录位置。同样的,与书中差异在于需要多加一个:p,即:edit %:p:h<Tab>
当列出路径后,再输入文件首字母,就能继续用Tab键补全文件名。

find命令

通过编辑path参数,我们可以使用find命令来查找文件。

:set path+=app/**

这条命令在path中添加了app/下的所有子目录(**通配符),此时,我们可以使用:find Navigator.js在path路径下寻找并打开该文件,如果不存在,会有报错。
如果在命令行中,输入:find Navigater.js<Tab>的话,Vim会将第一个找到的同名文件路径补全出来,再按一次会显示下一个匹配的路径文件。

netrw

在Vim中输入:E可以进入Vim自带的explorer插件,可以在里面像资源管理器一样浏览、修改目录和文件,可以通过回车选中想要编辑的文件,直接在buffer中打开。
关于怎么在不选中任何文件的情况下退出,书中写使用<C-^>,可是一直没有尝试成功。查了资料之后,发现可以使用:bd删除当前buffer(也就是说explorer也是个buffer),回到上一个编辑界面。
P.S 这个问题用百度查了半天没结果,google了一下马上就知道了..也是一时脑残就不该问百度技术问题。

如果要向一个不存在的目录保存文件,需要配合shell命令创建文件夹:

:!mkdir -p %:p:h
:write
用超级用户权限保存文件

在一个权限管理比较严格的环境,一般我们都使用非root权限进行登录,当修改一些需要root权限才能修改的文件时,常常忘记切换用户,改了半天才发现无法保存,这时候是不是只能关闭文件--切换用户--重新打开编辑?
其实配合shell命令,我们可以直接在Vim中实现超级用户权限保存文件的功能:

:w !sudo tee % > /dev/null

实现原理是,write命令把文件内容作为输入提供到后续的shell命令,sudo tee %..这里的%就跟上面几个例子中提到的一样,代表当前编辑的文件(使用%:p应该也是一样的),然后把stdout输出到黑洞去。

V 文件内的快速跳转

没有什么需要特别说明的,都是一些熟能生巧的操作,记录备查,在操作中多使用就好了:

图5-1 g命令的作用
�图5-2 基于单词的跳转

还有一个W命令,与w不同在于它是根据词边空格来判断一段词的结束,而w只是识别到一个单词,在例如 it's/you're这样的词组情况下,就能体现出不同了。

图5-3 f命令

f命令用于根据字母跳转到响应位置,使用一次后,";"用于跳转到下一个,而","跳转到上一个。

d{motion}可以删除到motion位置的内容:
dt. 删除当前位置到第一个.的内容
d/ge<CR> 删除当前位置到第一个"ge"位置的内容

使用vit等命令,选中某个区域中的内容,这种方法适用于各种普通命令,比如d/c/y等等。

图5-4 vi/a{tag}

图5-5 边界文本对象

考虑到空格的情况,一般来说,d命令都适用于aw as。c命令适用于iw is。

mark与跳转

使用m{a-zA-Z}命令可以设置mark点,然后可以通过'或者来进行跳转,不同在于'跳转到mark所在句子的第一个非空格位置,而直接跳转到精确的位置上。
有一些mark位置是自动的:

图5-6 自动mark标记位置

我们还可以用%立刻匹配到相对应的闭合符号处:

图5-7 使用%跳转到对应的闭合符号
图5-8 跳转操作实例
jump和change

:jumps可以看到跳转的记录
<C-o>/<C-i>可以用来向后/向前跳转到相应的文件。

图5-8 跳转操作汇总

使用:changes可以查看到文档中发生修改的记录。
使用g;和g,可以在最近修改的位置之间发生跳转。

VI 寄存器和宏命令

Vim的复制/剪切/粘贴操作都涉及到寄存器的概念。
最常用也最感受不到的寄存器,被称为无名寄存器。我们不指明使用的寄存器的时候,使用的就是无名寄存器。
但是有时候只用无名寄存器会存在问题,比如下面这个例子:
y、d、p都用到无名寄存器,因此无法实现我们想要做到的目标。


图6-1 无名寄存器的不足

这时候就需要使用到指名的寄存器,使用"可以指定寄存器来进行操作。其实对于Yank,它会有一个独立的寄存器--"0寄存器。因此对于上面这个例子,可以这么操作:

图6-2 "0寄存器

或者我们可以在删除的时候使用黑洞寄存器("_寄存器),这样就不会覆盖无名寄存器。

图6-3 黑洞寄存器
图6-4 其他寄存器

在插入模式下,可以使用<C-r>{register}来paste寄存器中的内容。
插入模式的<C-r>0等同于"0p

宏命令

这里把宏命令和寄存器放在一起,因为宏也是存放在寄存器中然后调用执行的。
我们使用q{register}命令来开启记录模式,把后面的操作记录到指定寄存器中。


图6-5 宏命令的保存

使用:reg a,可以看到寄存器a中的内容。

图6-6 通过寄存器调用宏

我们一样可以通过@{register}调用寄存器中的一段宏,也可以用@@来重复上一次调用寄存器的操作。

.命令可以用于重复执行命令,但是不能为.命令指定次数,这时候就可以利用marcro

图6-7 使用宏命令多次执行.命令

执行宏命令还可以分为串行和并行模式两种情况。

考虑下面这样一个宏命令

图6-8 宏命令的串行和并行

如果我们要串行执行这个宏命令,直接使用N@a就可以了,就像这样

图6-9 串行执行宏命令

但是这种执行有一个缺点,如果在执行过程中,f.这个命令没有找到内容,会产生内部错误导致命令执行中断,下例中,执行到第三行的时候就出现了问题,导致后面的内容没有被改变:

图6-10 串行命令的中断

这时候我们可以改变策略,使用并行执行的操作。方法是先一样录制宏命令后,通过可视模式选中要修改的行来进行并行宏命令操作。


图6-11 并行执行命令

使用并行执行是一种更健壮的选择,但是有时候我们需要在执行中得到可能发生的错误的提示,这时候,采用串行执行就是一个更好的选择。所以具体用哪种方法,是要根据情况进行选择的。

对一批文件执行宏命令

同样也分为两种模式:串行和并行。
大致思路是:
并行模式
通过:argdo normal @{register}来批量执行。
要注意的是,这个方式下,会对用来录制脚本的第一个文件进行重复的修改,我们要在执行argdo之前,使用:edit!来把第一个文件的修改全部回退掉。然后进行批量执行。
串行模式
在宏命令中,显式地录制:next命令,跳转到arglist中的下一个文件里去。再重复执行相应的命令。

注意在这里选择那种模式执行,和单一文件内执行有所不同,由于不能一眼看到执行的结果和错误,如果选择并行模式,对于出现报错的情况,需要一一查看所有的文件来确认情况。而串行模式下,执行会停止在出错的位置,方便我们判断。如何取舍,要自己判断。

下面是一段为代码封装一层module的宏,紧凑实用,记录备查


�图6-12 添加代码封装的宏
通过宏生成一个计数器
图6-13 录制宏生成计数器
图6-14 利用宏给各行添加数字标记
修改宏的内容

使用qA命令,可以在a寄存器的现有内容之后继续添加内容。这种方法只能追加,如果想要在宏的中间添加内容,就要用另一种办法。
思路是这样的:利用寄存器其实就是记录一段命令操作内容的原理,将录制好的寄存器内容黏贴出来进行修改,然后再拷贝回原来的寄存器里去。
拷贝的方式也有两种,一种直接"{register}dd,整行剪切进去,这样的做法一般没有问题,但是末尾会有一个回车,有时候可能会导致宏的操作被干扰,更保险的做法是按字符来进行黏贴。

图6-15 将黏贴出来的寄存器内容进行修改
图6-16 使用"ay$按字符形式保存到寄存器

VII 模式、搜索与替换

magic search 和 very magic search

使用/来进行模式匹配时,默认是使用magic search。对于一些特殊的符号,如果要按照特殊意义来使用,则需要用\来转义,比如()和{}。注意,我们只需要转义前一个符号,后面那个会自动转义。有时候这样做太麻烦了,于是我们可以使用\v来进入very magic search,这类符号就不需要转义操作了。具体区别,可以参看下表

图7-1 magic search和very magic search

注意,在very magic模式下,Vim会将一切符号认作是特定符号,除了 _ 字母 数字以外。但上例中的#,我们是按普通内容匹配的,为什么在very magic下也没有转义还是按普通内容匹配了呢?
Vim的解释是,所有暂时没有被分配特殊意义的字符,会按照字面来匹配,#暂时不代表特殊意义,因此无需转义。如果今后#被使用为特殊符号,那在very magic模式下匹配#就需要用#了。

verynomagic 模式

.,*,?这些特殊的正则符号,在进行正则匹配时不可或缺十分好用,但是如果我们想在pattern匹配中匹配这些符号的字面意思,就需要转义进行,当符号多的时候就很麻烦。这时候我们应该使用\V进入verynomagic或者叫Verbatim搜索模式,此时这些特殊正则符号无需转义,就按照字面来匹配。

图7-2 Verbatim search
使用()来捕获次级匹配

我们来看一下这么一个匹配
/\v<(\w+)_s+\1>
这里的<>用来匹配边界,_s+代表匹配空白符或回车,重点是这里的\1,匹配的是前面用()包围的部分\w+,对于要反复使用匹配模式,特别是匹配模式比较复杂的情况下,用这种次级匹配就会比较方便。

对于用<>来确定单词边界,也是一个很有用的技巧:
对于下面的内容如果我们要找出the这个单词,使用/the来匹配,会发现匹配到许多不需要的东西,因为they、their中的the也被匹配到了。
the problem with these new recruits is that
they don't keep their boots clean.
这时候如果用/\v<the>来匹配,就能正确地获得the这个单词了。

有时候我们使用()来进行分组,比如下面这个匹配:
/\v(And|D)rew Neil
我们使用()来匹配Andrew或者Drew,但是我们对于是And和D这两个模式并不关心,也不会用到,如果就这么占用了一个\1寄存器,会很浪费。这时候我们可以用%来让Vim不要记录这个模式
/\v%(And|D)rew Neil

下面这个例子可以将匹配到的内容再进行对调:
/\v(%(And|D)rew) (Neil)
:%s//\2, \1/g

图7-3 常用的atom
调整匹配的边界

有时候我们匹配一段内容,但是实际想要修改或操作的只是其中一部分,这时候我们可以用\zs和\ze来调整匹配的边界,下例中,将两边的"排除在外了

图7-4 调整边界
一个综合的搜索例子

对于下面这个文本,我们想要搜索[]内的内容,应该怎么做呢。

Search items: [http://vimdoc.net/search?q=/\\]

首先,我们可以将[]内的内容复制下来。使用i[的技巧,将光标放置到[]之内的任何地方,然后"uyi[,就可以将[]中的内容复制到u寄存器。然后进入匹配模式,输入
/\V<C-r>u就能发现,从u寄存器中将内容读取到了匹配模式中。
生成的查询语句如下:

/\Vhttp://vimdoc.net/search?q=/\\

但这个时候会发现,实际只匹配到了http:这个内容,那是因为第二个/就把这个匹配过程终结了。

这时候,我们有几种选择,一种是将所有的/转义:
**/\Vhttp://vimdoc.net/search?q=/\\\\ **

如果这样觉得太麻烦,可以选择用?进行反向搜索,这时候第二个?成了搜索终结的符号,只需要将?转义就可以了:
?http ://vimdoc.net/search?q=/\\\\

注意,无论哪种搜索模式,\都是需要被转义的

统计匹配次数的小技巧

利用替换命令,可以有个小技巧用于统计一个匹配模式的出现次数。
当我们先获得了一个匹配后,可以用如下命令
:%s///gn
来获取模式的匹配次数。这条命令如果没有最后的n,会把所有的之前的匹配模式替换为空字符,最后的n会抑制住这个替换操作,Vim下放会显示这个模式一共有多少个,出现在多少行中。

搜索后光标位置

使用/e在搜索后将光标停止在搜索的尾部:


图7-5 搜索后将光标置于搜索的尾部
一步步完善搜索

对于下面文档,如果我们想把用于引用的'替换为",应该怎么做呢?

This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'

首先,应该想办法把这个模式匹配出来:
/\v'.+'

结果发现,匹配成了这样:

This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'

优化一下:
/\v'[ ^']+'

This string contains a 'quoted' word.
This string contains 'two' quoted 'words.'
This 'string doesn't make things easy.'

再优化一下:
/\v'([ ^']|'\w)+'
至此,匹配成功。

为了替换方便,我们可以再改造一下,变成
/\v'(([ ^']|'\w)+)'
然后使用
:%s//"\1"/g
就可以达成我们的目标了。注意这里的一个技巧,%s//就是使用上一次的匹配模式结果,避免我们重复进行输入。

匹配和替换过程中,特别是复杂的模式,不要想着一次写成,可以多尝试几次,慢慢完善。

图7-6 替换中常用的一些字符
flags

最常用的替换,我们应该很熟悉:
:%s/pattern/sub/g
这里的%指的是全文每行都要进行替换,而结尾的g flag,则指要将一行内所有匹配到的都替换。除了g之外,还有很多的flag,比如之前我们用过的n flag取消实际的替换操作,还有c flag,在每次替换前都询问我们是否要进行替换操作。

图7-7 针对c flag的应答
重复替换操作

加入我们执行了下面这个替换:
:s/target/replacement/g
我们马上意识到了问题,没有%,这个操作就只在当前行生效。这时候,我们不需要修改命令,增加%来执行替换,只要简单地使用g&命令,就可以达到同样的效果。
g&命令,相当于:%s//~/&,// ~ /&分别表示使用上一次的匹配模式,替换成上一次进行替换用的内容,同时使用上一次的flag。
这个命令也可以用:%&&来代替,第一个&表示复用上一次的替换,后一个&表示复用上一次的flag。下面有个例子,在使用可视模式替换时,:&&很好用。

图7-8 第一次替换把所有内容都改了
图7-9 撤回后利用gv和&&快速执行替换
从寄存器获取替换内容

有两种方式,一种是通过值传递
:%s//<C-r>0/g 直接将0 寄存器的内容读取到命令中,好处是直接能看到,缺点在于如果其中有些特殊字符,可能需要修改转义。
还有一种,通过引用传递
:%s//=@0/g
0寄存器保存着上一次复制的内容。

替换时进行数学运算

假设对于以下这样的文本,我们想要把每一级的标题级别,都提升一级

<h2>Heading number 1</h2>
<h3>Number 2 heading</h3>
<h4>Another heading</h4>

使用这个匹配:
/v</?h\zs\d就能直接匹配到<>内的数字,这里又用到了\zs的技巧。
替换命令:
:%s//=submatch(0)-1/g
这里的submatch(0)就是代表了上一次的匹配内容。

交换两个词

如果要将两个词做对调,使用普通的替换操作是做不到的,一种方法是用字典来实现:
假设我们要替换dog和man这两个词。
先让我们看看字典是怎么一回事:


图7-10 字典
图7-11 具体实现
在多个文件中替换

最简单粗暴的办法,就是将可能需要替换的文件全部读入arglist,然后一次性argdo
匹配模式 /Pragmatic\ze Vim
读入文件 :args **/.txt*
批量执行 :argsdo %s//Practical/g
忽略错误批量执行 :argsdo %s//Practical/ge

global命令

大名鼎鼎的:g命令,格式如下:

:[range] global[!] /{pattern}/ [cmd]

原理是,在range范围内,根据pattern标记出所在的行,针对这些行,执行cmd命令的内容。相对应的有:v命令,是对除了pattern之外的行进行标记,并执行cmd命令。

例如
:g/good/d
这个命令,就会将所有含有good的行删除掉。

sort内容

对于类似下面这样的文件内容,如果我们要对{}中的各行根据首字母排序

html {margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
line-height: 1.5;
color: black;
background: white;

}
body {
line-height: 1.5;
color: black;
background: white;
}

:g/{/ .+1,/}/-1 sort 这么一条命令就够了。

相应的,如果要对一些行进行缩进操作:
:g/{/sil .+1,/}/-1 > 就可以实现
这里的/sil用于屏蔽命令的返回。

只要充分掌握好pattern匹配和各类命令,:g可以实现许多强大的功能。

推荐阅读更多精彩内容