模式(pattern)是Vim中一个非常重要的概念,Vim命令 /:global:substitute 等均要用到 pattern 的功能。

vim根据 pattern 来匹配文本,可通过 pattern 来指定Vim命令的操作对象。与 pattern 关系密切的正则表达式语法不是本文的重点,读者可以通过其他途径学习正则表达式,但跟Vim正则表达式相关的内容,推荐阅读Vim搜索字符转义与very magic搜索模式

1. Vim模式与子匹配

Vim模式相关概念比较抽象,为了对其进行准确的描述,本文以一个基本的Vim替换命令 :%s/\v(^$\n){2,}/\1\1 为例来进行介绍。

Vim替换命令substitute小结一文可知,substitute命令的基本语法是 :[range]s/pattern/目标串/[option]

在这条替换命令中,除 \v 外,出现了三个新的符号: (){2,} 以及 \1

首先,Vim模式中的特殊字符 () 用来标识一个 子模式,任何 () 内部的匹配文本被称作为一个 子匹配,都会被自动保存到一个临时的仓库。

其次,可以用 \1\2 …… \9 来依次引用被每对 () 捕获的子匹配;不论模式中是否使用了圆括号,元字符 \0 永远会引用整个匹配。

最后,{n,m} 是用于 pattern 的修饰符号,表示倍数项,意思是匹配 n 个以上匹配源,且尽可能多地进行匹配,但最多匹配不超过 m 个匹配源。在Vim中,通过命令 :h /multi 可以查看更加详细的介绍。

因此,:%s/\v(^$\n){2,}/\1\1 中第一个子模式为 ^$\n,表示一个空白行,可以用 \1 进行引用;{2,} 表示匹配连续两个以上的空白行,且匹配次数无上限;而替换命令substitute的目标模式为 \1\1,表示两个 ^$\n

所以,Vim命令 :%s/\v(^$\n){2,}/\1\1 是将整个文档( % )中连续两个以上的空行替换成两个空行

Vim-pattern

2. 使用子匹配进行列重排

为了加深对Vim模式和子匹配的理解,本文继续介绍一个应用Vim子匹配的实例。

假设有一个 CSV 格式的文件,其中包含了一份含有电子邮箱地址以及姓名的列表,部分数据如下所示。

1
2
3
4
last name,first name,email
neil,drew,drew@vimcasts.org
doe,john,john@example.com
yuan,ayawaw,admin@vim.ink

现在假设想交换这些字段的次序,把电子邮箱放到首列,其次是名字,最后一列为姓氏。

这是一个典型的列重排任务,可以使用Vim的 substitute 命令完成。

为了获取到 substitute 命令的操作源模式,首先使用Vim查找命令 / 构建正确的匹配模式 /\v^([^,]*),([^,]*),([^,]*)$

在这个模式中,[^,] 会匹配除 , 以外的任何字符。因此,([^,]*) 不仅会匹配 0 次或多次连续的非 , 字符,而且会把捕获到的结果当作子匹配。

因此,搜索命令 /\v^([^,]*),([^,]*),([^,]*)$ 会匹配到原始 CSV 文件中的每一行,而且原始文件中的姓、名、邮箱3列分别可通过 \1\2\3 进行引用。

在此基础上,可以使用Vim替换命令 :%s//\3,\2,\1 将原始文件重排成按邮箱、名字、姓氏属性进行排列。

对于 :%s//\3,\2,\1 命令,由于其源pattern为空,因此会使用最近一次的查找模式(参考Vim替换命令substitute重用上次的查找模式),即 \v^([^,]*),([^,]*),([^,]*)$,而其目标字符串为 \3,\2,\1,扩展开来的目标串格式为:源pattern中的子匹配3,源pattern中的子匹配2,源pattern中的子匹配1。而这一目标串格式正好能完成列重排的目标。

vim-pattern

嗯,扫一扫就可以找到小女子我啦~