声明

本文搬运自Github仓库Learn-Vim_zh_cn,并针对部分错别字做出修正。

到目前为止,您已经了解了如何使用点命令(.)重复上一次更改,如何使用宏(q)重复动作以及将文本存储在寄存器中(")。

在本章中,您将学习如何在全局命令中重复命令行命令。

全局命令概述

Vim的全局命令用于同时在多行上运行命令行命令。

顺便说一句,您之前可能已经听说过 “Ex命令” 一词。在本书中,我将它们称为命令行命令,但Ex命令和命令行命令是相同的。它们是以冒号(:)开头的命令。在上一章中,您了解了替代命令。这是一个Ex命令的示例。它们之所以称为Ex,是因为它们最初来自Ex文本编辑器。在本书中,我将继续将它们称为命令行命令。有关Ex命令的完整列表,请查看:h ex-cmd-index

全局命令具有以下语法:

1
:g/pattern/command

pattern匹配包含该模式串的所有行,类似于替代命令中的模式串。command可以是任何命令行命令。全局命令通过对与pattern匹配的每一行执行command来工作。

如果您具有以下表达式:

1
2
3
4
5
6
7
8
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

要删除所有包含"console"的行,可以运行:

1
:g/console/d

结果:

1
2
3
4
5
const one = 1;

const two = 2;

const three = 3;

全局命令在与"console"模式串匹配的所有行上执行删除命令(d)。

运行g命令时,Vim对文件进行两次扫描。在第一次运行时,它将扫描每行并标记与/console/模式传教匹配的行。一旦所有匹配的行都被标记,它将进行第二次运行,并在标记的行上执行d命令。

如果要删除所有包含"const"的行,请运行:

1
:g/const/d

结果:

1
2
3
4
5
console.log("one: ", one);

console.log("two: ", two);

console.log("three: ", three);

逆向匹配

要在不匹配的行上运行全局命令,可以运行:

1
:g!/{pattern}/{command}

或者

1
:v/{pattern}/{command}

如果运行:v/console/d,它将删除 包含"console"的所有行。

模式串

全局命令使用与替代命令相同的模式串系统,因此本节将作为更新。随意跳到下一部分或继续阅读!

如果您具有以下表达式:

1
2
3
4
5
6
7
8
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

要删除包含"one"或"two"的行,请运行:

1
:g/one\|two/d

要删除包含任何一位数字的行,请运行以下任一命令:

1
:g/[0-9]/d

或者

1
:g/\d/d

如果您有表达式:

1
2
3
const oneMillion = 1000000;
const oneThousand = 1000;
const one = 1;

要匹配包含三到六个零的行,请运行:

1
:g/0\{3,6\}/d

传递范围参数

您可以在g命令之前传递一个范围。您可以通过以下几种方法来做到这一点:

  • :1,5/g/console/d 删除第1行和第5行之间匹配字符串"console"的行。
  • :,5/g/console/d 如果逗号前没有地址,则从当前行开始。它在当前行和第5行之间寻找字符串"console"并将该行删除。
  • :3,/g/console/d 如果逗号后没有地址,则在当前行结束。它在第3行和当前行之间寻找字符串"console"并将该行删除。
  • :3g/console/d 如果只传递一个地址而不带逗号,则仅在第3行执行命令。在第3行查找,如果包含字符串"console",则将其删除。

除了数字,您还可以将这些符号用作范围:

  • . 表示当前行。范围.,3表示当前行和第3行之间。
  • $ 表示文件的最后一行。 3,$范围表示在第3行和最后一行之间。
  • +n 表示当前行之后的n行。您可以将其与.结合使用,也可以不结合使用。 3,+13,.+1表示在第3行和当前行之后的行之间。

如果您不给它任何范围,默认情况下它将影响整个文件。这实际上不是常态。如果您不传递任何范围,Vim的大多数命令行命令仅在当前行上运行(两个值得注意的例外是:这里介绍的全局命令(:g)和save(:w)命令)。

普通模式命令

您可以将全局命令和:normal命令行命令一起运行。

如果您有以下文字:

1
2
3
4
5
6
7
8
9
const one = 1
console.log("one: ", one)

const two = 2
console.log("two: ", two)

const three = 3
console.log("three: ", three)

要添加";"运行到每一行的末尾:

1
:g/./normal A;

让我们分解一下:

  • :g 是全局命令。
  • /./ 是“非空行”的模式。它匹配至少包含1个字符的行。因此将与包含“const”和“console”的行匹配。它不匹配空行。
  • normal A; 运行:normal命令行命令。 A; 是普通模式命令,用于在该行的末尾插入";"。

执行宏

您也可以使用全局命令执行宏。宏只是普通模式下的操作,因此可以使用:normal来执行宏。如果您有以下表达式:

1
2
3
4
5
6
7
8
const one = 1
console.log("one: ", one);

const two = 2
console.log("two: ", two);

const three = 3
console.log("three: ", three);

请注意,带有"const"的行没有分号。让我们创建一个宏,以在寄存器"a"的这些行的末尾添加逗号:

1
qa0A;<esc>q

如果您需要复习,请查看有关宏的章节。现在运行:

1
:g/const/normal @a

现在,所有带有"const"的行的末尾将带有";"。

1
2
3
4
5
6
7
8
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

如果您一步一步按照示例做,您将会在第一行末尾看到两个分号。为避免这种情况,使用全局命令时,给一个范围参数,从第2行到最后一行, :2,$g/const/normal @a

递归全局命令

全局命令本身是命令行命令的一种,因此您可以从技术上在全局命令中运行全局命令。

给定表达式:

1
2
3
4
5
6
7
8
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

如果您运行:

1
:g/console/g/two/d

首先,g将查找包含模式"console"的行,并找到3个匹配项。然后,第二个"g"将从那三个匹配项中查找包含模式"two"的行。最后,它将删除该匹配项。

您也可以将gv结合使用以找到正负模式。例如:

1
:g/console/v/two/d

与前面的命令不同,它将查找 包含"two"的行。

更改定界符

您可以像替代命令一样更改全局命令的定界符。规则是相同的:您可以使用任何单字节字符,但字母,数字,", |, 和 \除外。

要删除包含"console"的行:

1
:g@console@d

如果在全局命令中使用替代命令,则可以有两个不同的定界符:

1
g@one@s+const+let+g

此处,全局命令将查找包含"one"的所有行。 替换命令将从这些匹配项中将字符串"const"替换为"let"。

默认命令

如果在全局命令中未指定任何命令行命令,会发生什么?

全局命令将使用打印(:p)命令来打印当前行的文本。如果您运行:

1
:g/console

它将在屏幕底部打印所有包含"console"的行。

顺便说一下,这是一个有趣的事实。因为全局命令使用的默认命令是p,所以这使g语法为:

1
:g/re/p
  • g = 全局命令
  • re = 正则表达式模式
  • p = 打印命令

这三个元素连起来拼写为 “grep”,与命令行中的grep 相同。但这 是巧合。 g/re/p命令最初来自Ed编辑器(一个行文本编辑器)。 grep命令的名称来自Ed。

您的计算机可能仍具有Ed编辑器。从终端运行ed(提示:要退出,请键入q)。

反转整个缓冲区

要翻转整个文件,请运行:

1
:g/^/m 0 

^表示行的开始。使用^匹配所有行,包括空行。

如果只需要反转几行,请将其传递一个范围。要将第5行到第10行之间的行反转,请运行:

1
:5,10g/^/m 0

要了解有关move命令的更多信息,请查看:h :move

汇总所有待办事项

当我编码时,有时我会想到一个随机的绝妙主意。不想失去专注,我通常将它们写在我正在编辑的文件中,例如:

1
2
3
4
5
6
7
8
9
10
11
const one = 1;
console.log("one: ", one);
// TODO: 喂小狗

const two = 2;
// TODO:自动喂小狗
console.log("two: ", two);

const three = 3;
console.log("three: ", three);
// TODO:创建一家销售自动小狗喂食器的初创公司

跟踪所有已创建的TODO可能很困难。 Vim有一个:t(copy)方法来将所有匹配项复制到一个地址。要了解有关复制方法的更多信息,请查看:h :copy

要将所有TODO复制到文件末尾以便于自省,请运行:

1
:g/TODO/t $

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const one = 1;
console.log("one: ", one);
// TODO:喂小狗

const two = 2;
// TODO:自动喂小狗
console.log("two: ", two);

const three = 3;
console.log("three: ", three);
// TODO:创建一家销售自动小狗喂食器的初创公司

// TODO:喂小狗
// TODO:自动喂小狗
// TODO:创建一家销售自动小狗喂食器的初创公司

现在,我可以查看我创建的所有TODO,另外找个时间来完成它们,或将它们委托给其他人,然后继续执行下一个任务。

如果不想复制,而是将所有的 TODO 移动到末尾,可以使用移动命令 m

1
:g/TODO/m $

结果:

1
2
3
4
5
6
7
8
9
10
11
12
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

// TODO:喂小狗
// TODO:自动喂小狗
// TODO:创建一家销售自动小狗喂食器的初创公司

黑洞删除

回想一下寄存器那一章,已删除的文本存储在编号寄存器中(允许它们足够大)。每当运行:g/console/d时,Vim都会将删除的行存储在编号寄存器中。如果删除多行,所有编号的寄存器将很快被填满。为了避免这种情况,您可以使用黑洞寄存器("_ 将删除的行存储到寄存器中。

1
:g/console/d _

通过在d之后传递_,Vim不会将删除的行保存到任何寄存器中。

将多条空行减少为一条空行

如果您的文件带有多个空行,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
const one = 1;
console.log("one: ", one);


const two = 2;
console.log("two: ", two);





const three = 3;
console.log("three: ", three);

您可以快速将多个空行减少为一条空行。运行:

1
:g/^$/,/./-1j

结果:

1
2
3
4
5
6
7
8
const one = 1;
console.log("one: ", one);

const two = 2;
console.log("two: ", two);

const three = 3;
console.log("three: ", three);

一般情况下全局命令遵循下列格式::g/pattern/command。但是,您也可以使用下面的格式::g/pattern1/,/pattern2/command。用这种格式,Vim将会使command作用在pattern1pattern2上。

记住上面说的格式,让我们根据:g/pattern1/,/pattern2/command这个格式分解一下命令:g/^$/,/./-1j

  • /pattern1/ 就是 /^$/ 。它表示一个空行(一个没有任何字符的行)。
  • /pattern2/ 就是 /./(用-1作为行修正)。/./表示一个非空行(一个含有至少1个字符的行)。这里的 -1 意思是向上偏移1行。
  • command 就是 j,一个联接命令(:j)。在这个示例中,该全局命令联接所有给定的行。

顺便说一句,如果您想要将多个空行全部删去,运行下面的命令:

1
:g/^$/,/./j

或者:

1
:g/^$/-j

您的文本将会减少为:

1
2
3
4
5
6
const one = 1;
console.log("one: ", one);
const two = 2;
console.log("two: ", two);
const three = 3;
console.log("three: ", three);

(译者补充:j连接命令的格式是::[range]j。比如::1,5j将连接第1至5行。在前面的命令中:g/pattern1/,/pattern2/-1j/pattern1//pattern2都是j命令的范围参数,表示连接空行至非空行上方一行,这样就会保留1个空行。在早前的英文版本中有关于j命令的介绍,不知为何在后面的更新中,原作者删除了关于j命令的介绍)

高级排序

Vim有一个:sort命令来对一个范围内的行进行排序。例如:

1
2
3
4
5
d
b
a
e
c

您可以通过运行:sort对它们进行排序。如果给它一个范围,它将只对该范围内的行进行排序。例如,:3,5sort仅在第三和第五行之间排序。

如果您具有以下表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arrayB = [
"i",
"g",
"h",
"b",
"f",
"d",
"e",
"c",
"a",
]

const arrayA = [
"h",
"b",
"f",
"d",
"e",
"a",
"c",
]

如果需要排序数组中的元素,而不是数组本身,可以运行以下命令:

1
:g/\[/+1,/\]/-1sort

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const arrayB = [
"a",
"b",
"c",
"d",
"e",
"f",
"g",
"h",
"i",
]

const arrayA = [
"a"
"b",
"c",
"d",
"e",
"f",
"h",
]

这很棒!但是命令看起来很复杂。让我们分解一下。该命令依然遵循 :g/pattern1/,/pattern2/command这个格式。

  • :g 是全局命令
  • /\[/+1 是第一个模式串,它匹配左方括号"["。+1表示匹配行的下面1行。
  • /\[/-1 是第二个模式串,它匹配右方括号"]"。-1表示匹配行的上面1行。
  • /\[/+1,/\]/-1 表示在"[“和”]"之间的行。
  • sort 是命令行命令:排序。

聪明地学习全局命令

全局命令针对所有匹配的行执行命令行命令。有了它,您只需要运行一次命令,Vim就会为您完成其余的工作。要精通全局命令,需要做两件事:良好的命令行命令词汇表和正则表达式知识。随着您花费更多的时间使用Vim,您自然会学到更多的命令行命令。正则表达式知识需要更多的实际操作。但是一旦您适应了使用正则表达式,您将领先于很多其他人。

这里的一些例子很复杂。不要被吓到。真正花时间了解它们。认真阅读每个模式串,不要放弃。

每当需要在多个位置应用命令时,请暂停并查看是否可以使用g命令。寻找最适合工作的命令,并编写一个模式串以同时定位多个目标。

既然您已经知道全局命令的功能强大,那么让我们学习如何使用外部命令来增加工具库。

链接