使用Vim宏实现复杂的查找与替换
涛叔最近准备翻译一篇文章。原文是 HTML 格式,译文需要改成 Markdown 格式。两种格式使用不同的标题锚点,前者使用驼峰加下划线,后者使用小写字母加短杠。所以需要将原文中的锚点批量转换成小字字母加短杠的形式。想来想去,还是用Vim宏实现比较方便。今天就分享给大家作为学习宏的实践素材😄
先看一下原文锚点:
<a href="#Understanding_costs">GC model</a>:
<a href="#Optimization_guide">optimization guide</a>.
转成 Markdown 后需要替换为:
<a href="#understanding-costs">GC model</a>:
<a href="#optimization-guide">optimization guide</a>.
最简单的办法是处理两遍。第一遍先把锚点都转为小写字母。第二遍再把下划线替换成短杠。
我们先通过搜索找到要处理的文本,搜索指令为:
/\v"#\w+"
/
表示搜索。\v
比较特别,表示这下来使用 magic 模式,其实就是标准正则模式。在该模式下+
表示的就是正则中至少出现一次的意思。如果不用\v
,则需要写成\+
。然后我们用"#\w+"{regex}
匹配原文中的锚点,其中\w
等价于[a-zA-Z0-9_]
。
搜索后 Vim 会跳转到第一个匹配的位置。然后我们只需要执行gui"
就可以把双引号内的字符全部改为小写。
我们分解一下刚才的指令。首先,gu
表示将字符转小写,但输入后还需要指定修改范围。这个范围可以通过 text object 确定。这里我们用的是i"
,它表示双引号内部的所有内容,但不含引号本身。如果想包含双引号,则可以使用a"
。有人可能会问为什么要区分i"
和a"
。在当前的场景下确实没有区别。但 text object 还可以跟其他指令配合。以删除为例,di"
会删除双引号里面的内容,但保留引号;如果想连引号一块删除,则可以用da"
。
接下来按n
跳转到下一个锚点,再按.
重复执行上一次修改(也就是转小写)。不断重复,直到改完为止。到这里我们就完成第一遍处理。
第二遍处理就稍微有点复杂了。我们知道,替换使用s
指令。比如s/_/-/
只会替换当前行出现的第一个下划线。如果要替换当前行所有的下划线,需要改成s/_/-/g
,也就是在最后面追加g
参数。但是这两种操作都不行,因为我们只想替换双引号内部所有的下划线😂
怎么办呢?我想到的是先用 visual 模式选中引号中的内容,然后再用'<
和'>
获取上次选中的范围,最后用s
完成替换。完整指令为:
'<,'>s/_/-/g
这样就可以只替换双引号内部的的下划线了😄所以第二遍的处理过程是搜索、替换,然后按n
跳到下一个位置,再按:&
重复上一次替换。
注意,.
无法重复执行替换指令⚠️
以上是传统的处理方案。考虑到锚点数量太多,分两遍处理有点傻,所以就引出本文的主要内容——使用宏将两种操作合并。
所谓宏,简单来说就是按键记录器。Vim录制宏的本质是把用户输入的字符记录下来。在回放宏的时候再替用户重复按一下键盘。就这么简单单。又因为Vim所有的操作都需要通过键盘控制,所以Vim的宏可以录制全部功能。
这里的思路是使用宏依次录制如下操作:
- 转小写
gui"
- 选中
vi"^[
- 替换
:'<,'>s/_/-/g^m
- 搜索
/\v"#\w+"^M
录制宏需要选一个寄存器,一般可以选a-z
这26个。比如我用寄存器a
。
在录制之前我们先跳转到一个锚点上,准备好。
开始录制指令为qa
。此时 Vim 为显示recording @a
。接下来所有的操作都会被保存到a
中。
然后执行gui"
将当前锚点转成小写字母。再执行vi"
选中当前锚点,然后按Esc
(上面的^[
)退出 visual 模式。虽然退出了,但'<
和'>
还是会保存上次选中的范围。接着执行:'<,'>s/_/-/g
完成替换。上面的^m
表示回车。最后执行搜索/\v"#\w+"
跳转到下一个锚点。
所有动作都完成后按q
完成录制。我们可以执行reg a
查看刚才录制的内容:
:reg a
Type Name Content
c "a gui"vi"^[:'<,'>s/_/-/g^M/\v"#\w+"^M
Press ENTER or type command to continue
到这里,准备工作结束,可以摘果子了😄回放宏使用@a
。因为我用了寄存器a
,所以转入@a
。Vim 一番操作后会跳转到下一个锚点。再次执行@a
,会重复刚才的替换。我们还可以按@@
再重复执行@a
,加快速度。
最后提一下,录制宏一定要「谋定而后动」。宏一定是一系列可重复执行的操作。以本文为例,我是先完成替换再跳转到下一个锚点,这就为重复执行做好准备。如果没有最后的搜索,那在回放之后还得自行跳转,效果就会打折。
我之前写过一篇宏的入门文章,也欢迎阅读。