对于一些简单的文本处理需求,使用Vim命令便可以快速实现。在使用Vim提取文本内容一文中,已经介绍了通过 :vglobal 命令来获取特定文本行以及使用 :global 命令将 CSS 文件中所有规则的属性按字母排序。

本文继续介绍使用Vim的 :global 命令来提取所有奇数行或所有偶数行文本,达到奇偶行删除的目的。使用 :normal 命令实现奇偶行删除的内容,推荐阅读Vim处理文本之使用normal命令实现奇偶行删除

假设有一段如下的《泰坦尼克号》电影对白,需要分别提取出ROSE和JACK的所有台词,并分别保存到两个文件中。

1
2
3
4
5
6
7
8
9
ROSE: I love you Jack.
JACK: No... don't say your good-byes, Rose. Don't you give up. Don't do it.
ROSE: I'm so cold.
JACK: You're going to get out of this... you're going to go on and you're going to make babies and watch them grow and you're going to die an old lady, warm in your bed. Not here. Not this night. Do you understand me?
ROSE: I can't feel my body.
JACK: You must do me this honor... promise me you will survive... that you will never give up... no matter what happens... no matter how hopeless... promise me now, and never let go of that promise.
ROSE: I promise.
JACK: Never let go.
ROSE: I promise. I will never let go, Jack. I'll never let go.

分析上面待处理的文本内容可以发现,ROSE的台词总是在 1、3、5、7等奇数行,而JACK的台词总在2、4、6、8等偶数行,两人的台词是奇偶行交替出现。

因此,若想单独获取某个人的全部台词,实际上就是要提取出所有奇数行或者偶数行的台词。

Vim命令 :g/^/+d 可以用来删除所有偶数行,而命令 :g/^/d|m. 可以用来删除所有奇数行。

1. :g/^/+d 删除偶数行

下面,请跟我一起来分析上述两个简单的Vim命令行命令是如何实现奇偶行删除的功能的。

从Vim教程网上的文章Vim global命令和重复操作可知,global 命令的基本格式为 :[range]g[lobal][!]/{pattern}/[cmd]

其中,range 表示操作范围,global命令缺省操作范围是整个文件;pattern 用于指定 global 命令要匹配的目标模式;cmd 则表示除 global 命令外的任何 Ex 命令。

global 命令实现的效果是:在指定的 range 范围内,标记所有匹配 pattern 的文本行,并对匹配的行逐行执行 cmd 命令。

对于命令 :g/^/+d 来说,range 是使用缺省值 (整个文档);pattern 是 ^,表示查找所有行的开头;而待执行的 Ex 命令为 +d,是Vim命令 delete 的缩写形式。

:delete 命令的完整格式为 :[range]d[elete] [x],表示将 [range] 范围内的所有行删除到寄存器 x 中,缺省的 range 是当前行+d 表示删除当前行的下一行。具体介绍可参考Vim操作范围和文件范围

:在Vim中,除 global 命令缺省操作范围为全文档外,其他 Ex 命令的缺省操作范围都是当前行。

因此,当在Vim中执行 :g/^/+d 命令时,global 命令会从第一行开始执行,从而删除掉第二行。这时候,原本的第三行就会变成新的第二行,global 命令在新的第二行继续执行 +d,原本的第四行也会被删除掉……最终所有的偶数行都会被删除。

vim-删除偶数行

2. :g/^/d|m. 删除奇数行

接下来分析 :g/^/d|m. 是如何删除掉文件中的所有奇数行。

Vim在实际执行 global 命令时,会先查找所有匹配 {pattern} 的行,并对匹配结果进行标记,最后对每一个标记的行执行 Ex 命令

但是,如果被标记的行被删除、移动或者合并,这个标记就会被移除,对于标记被移除的行,就不会执行 ex 命令。

:g/^/d 可以理解为 在当前整个文档中执行 d 命令;而 | 用来分割命令,用于在一个行中同时输入多个命令;m 是Vim命令行命令 :move 的缩写形式,完整格式为 :[range]move{address},表示将 range 范围内的行文本移动到 address 指定的位置,m. 表示移动当前行(缺省range)到当前行(.),实际上不会造成任何文本变化。参考Vim复制命令copy和移动命令move

这个操作看起来很怪,其实是为了在执行完 d 操作后,移除下一行被global命令的 pattern 打上的标记,从而避免该行被执行 d 操作。

因此,:g/^/+d 命令首先对匹配的第一行执行删除操作,继续执行下一处匹配(即第二行)时,由于执行了 move 操作,global命令就会跳过这个新的 第一行,而是从 下下一行 再次执行 d命令,如此类推。最终删除掉原始文件中的所有奇数行。

vim删除奇数行

3. 小结

介绍了使用Vim :global 命令来实现奇偶行删除或奇偶行分离,重点需要理解 global 命令的执行原理和执行流程:global 命令会在指定的 range 范围内,标记所有匹配 pattern 的文本行,并对匹配的行逐行执行 Ex 命令。

此外,除 global 命令缺省操作范围为全文档外,Vim 其他 Ex 命令的缺省操作范围都是当前行。

这篇文章的主题基于知乎网友zecy写的一篇专栏文章而进行创作,特在此表示感谢。

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