跨行操作是日常工作中经常遇到的场景,最近在知乎上看到一个很好的讲解Vim跨行操作的案例,借鉴过来聊聊Vim的跨行操作。

简单来说,跨行操作是指一个Vim命令能同时操作多个文件行。在讲解跨行操作的具体案例时,需要理解下Vim的操作范围,推荐阅读Vim教程网之前介绍的两篇文章:ex命令操作范围Vim操作范围、文件范围介绍

1. 跨行删除

假设有一段从百度贴吧复制下来的纯文本内容,基本格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
1楼2016-08-04 20:24

举报 |
个人企业举报
垃圾信息举报

×_××
xxx
3

Vim教程网挺不错的,支持下


2楼2016-08-05 02:33

举报 |
个人企业举报
垃圾信息举报

××××
xxxxxx
吧主
13

嗯不错,支持原创,以后会经常访问https://vim.ink


3楼2016-08-05 12:18

举报 |
个人企业举报
垃圾信息举报

xxxxxxxx
l
1

支持原创,继续努力,妹子加油!

现在希望只把回帖内容保留下来,其他信息全部删掉,达到下面的效果:

1
2
3
4
5
Vim教程网挺不错的,支持下

嗯不错,支持原创,以后会经常访问https://vim.ink

支持原创,继续努力,妹子加油!

可以看到,作为起始部分的 楼层信息 格式是固定的,而作为结束部分的 头衔等级、空行 也是固定的。因此,可以用Vim pattern 匹配到 起始结束 的部分,绕开难以处理的用户名和贴吧头衔等内容。

1.1 使用substitute命令替换非目标数据

对于起始部分的 楼层信息,可以通过简单的正则表达式 ^\d\+楼.* 进行匹配;同样地,结束部分的 头衔等级和空行 也可以通过正则表达式 ^\d\+\n^$\n 进行匹配。但是,楼层信息和头衔等级中间的多行内容,很难用简单的正则表达式进行匹配。

对于正则表达式来说,元字符组合 .* 可以匹配到行内所有内容,却唯独不包括行尾字符,也就是说,.* 默认情况下是不匹配换行符的。所以,如果需要匹配起始行到结束行中间的任意多行,用 .* 无法实现。

为了匹配起始行和结束行中间的全部内容,需要使用 \_.\{-}

\_. 可以匹配到行尾字符,又因为 * 是贪婪匹配的,默认会匹配到最远的结束行,因此用 \{-} 指定作最少的匹配,保证匹配到的是最近的结束行。

所以,匹配上述起始部分和结尾部分所有内容的正则表达式为 ^\d\+楼\_.\{-}\n^\d\+\n^$\n,使用substitute命令替换非目标数据的最终命令为 :%s/^\d\+楼\_.\{-}\n^\d\+\n^$\n//g

为了更加清楚地理解上述命令的含义,将上面的substitute替换命令拆分为 /^\d\+楼\_.\{-}\n^\d\+\n^$\n:%s///g 两条命令。执行效果如下图所示。

vim跨行删除

1.2 使用global命令删除非目标数据

当然,有了1.1介绍的起始部分和结束部分的正则表达式,配合之前介绍的Ex命令通过模式指定操作范围global 命令的执行原理,我们可以用 Vim 命令 :g/^\d\+楼/,/^\d\+\n^$\n/d 达到与 1.1 相同的操作效果。

上述 global 命令的含义是:在全文范围内 (缺省 %) 匹配 ^\d\+楼 并标记每一行匹配结果,然后在每一行匹配文本上逐行执行命令 ,/^\d\+\n^$\n/d,从而删除了起始部分和结束部分之间的所有内容。

:这里 global 执行的命令使用了Ex命令 delete 的range形式,,/^\d\+\n^$\n/d 实际上等价于 .,/^\d\+\n^$\n/d,表示删除(d)当前行 (.) 到 匹配模式 /^\d\+\n^$\n/ 之间的所有行。

2. 跨行分离

有了1.2介绍的基础,通过Vim寄存器可将匹配模式的内容删除到某个寄存器中,从而实现跨行分离操作。

例如,:g/^\d\+楼/,/^\d\+\n^$\n/d A 将开始部分和结束部分中间的内容删除并保存到寄存器 a 中,并且是对寄存器内容进行追加 (因为使用了 A 形式)。

同样地,:g/^\d\+楼/,/^\d\+\n^$\n/m$ 可将开始部分和结束部分中间的内容依次移动到当前文档的末尾 (通过 move 命令实现,具体可参考Vim移动命令move)。

vim跨行分离

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