全局命令
到目前为止,您已经了解了如何使用点命令(.
)重复上一次更改,如何使用宏(q
)重复动作以及将文本存储在寄存器中("
)。
在本章中,您将学习如何在全局命令中重复命令行命令。
全局命令概述
Vim的全局命令用于同时在多行上运行命令行命令。
顺便说一句,您之前可能已经听说过 "Ex命令" 一词。在本书中,我将它们称为命令行命令,但Ex命令和命令行命令是相同的。它们是以冒号(:
)开头的命令。在上一章中,您了解了替代命令。这是一个Ex命令的示例。它们之所以称为Ex,是因为它们最初来自Ex文本编辑器。在本书中,我将继续将它们称为命令行命令。有关Ex命令的完整列表,请查看:h ex-cmd-index
。
全局命令具有以下语法:
:g/pattern/command
pattern
匹配包含该模式串的所有行,类似于替代命令中的模式串。command
可以是任何命令行命令。全局命令通过对与pattern
匹配的每一行执行command
来工作。
如果您具有以下表达式:
const one = 1;
console.log("one: ", one);
const two = 2;
console.log("two: ", two);
const three = 3;
console.log("three: ", three);
要删除所有包含"console"的行,可以运行:
:g/console/d
结果:
const one = 1;
const two = 2;
const three = 3;
全局命令在与"console"模式串匹配的所有行上执行删除命令(d
)。
运行g
命令时,Vim对文件进行两次扫描。在第一次运行时,它将扫描每行并标记与/console/
模式传教匹配的行。一旦所有匹配的行都被标记,它将进行第二次运行,并在标记的行上执行d命令。
如果要删除所有包含"const"的行,请运行:
:g/const/d
结果:
console.log("one: ", one);
console.log("two: ", two);
console.log("three: ", three);
逆向匹配
要在不匹配的行上运行全局命令,可以运行:
:g!/{pattern}/{command}
或者
:v/{pattern}/{command}
如果运行:v/console/d
,它将删除 不 包含"console"的所有行。
模式串
全局命令使用与替代命令相同的模式串系统,因此本节将作为更新。随意跳到下一部分或继续阅读!
如果您具有以下表达式:
const one = 1;
console.log("one: ", one);
const two = 2;
console.log("two: ", two);
const three = 3;
console.log("three: ", three);
要删除包含"one"或"two"的行,请运行:
:g/one\|two/d
要删除包含任何一位数字的行,请运行以下任一命令:
:g/[0-9]/d
或者
:g/\d/d
如果您有表达式:
const oneMillion = 1000000;
const oneThousand = 1000;
const one = 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,+1
或3,.+1
表示在第3行和当前行之后的行之间。
如果您不给它任何范围,默认情况下它将影响整个文件。这实际上不是常态。如果您不传递任何范围,Vim的大多数命令行命令仅在当前行上运行(两个值得注意的例外是:这里介绍的全局命令(:g
)和save(:w
)命令)。
普通模式命令
您可以将全局命令和:normal
命令行命令一起运行。
如果您有以下文字:
const one = 1
console.log("one: ", one)
const two = 2
console.log("two: ", two)
const three = 3
console.log("three: ", three)
要添加";"运行到每一行的末尾:
:g/./normal A;
让我们分解一下:
:g
是全局命令。/./
是“非空行”的模式。它匹配至少包含1个字符的行。因此将与包含“const”和“console”的行匹配。它不匹配空行。normal A;
运行:normal
命令行命令。A;
是普通模式命令,用于在该行的末尾插入";"。
执行宏
您也可以使用全局命令执行宏。宏只是普通模式下的操作,因此可以使用:normal
来执行宏。如果您有以下表达式:
const one = 1
console.log("one: ", one);
const two = 2
console.log("two: ", two);
const three = 3
console.log("three: ", three);
请注意,带有"const"的行没有分号。让我们创建一个宏,以在寄存器"a"的这些行的末尾添加逗号:
qa0A;<esc>q
如果您需要复习,请查看有关宏的章节。现在运行:
:g/const/normal @a
现在,所有带有"const"的行的末尾将带有";"。
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
。
递归全局命令
全局命令本身是命令行命令的一种,因此您可以从技术上在全局命令中运行全局命令。
给定表达式:
const one = 1;
console.log("one: ", one);
const two = 2;
console.log("two: ", two);
const three = 3;
console.log("three: ", three);
如果您运行:
:g/console/g/two/d
首先,g
将查找包含模式"console"的行,并找到3个匹配项。然后,第二个"g"将从那三个匹配项中查找包含模式"two"的行。最后,它将删除该匹配项。
您也可以将g
与v
结合使用以找到正负模式。例如:
:g/console/v/two/d
与前面的命令不同,它将查找 不 包含"two"的行。
更改定界符
您可以像替代命令一样更改全局命令的定界符。规则是相同的:您可以使用任何单字节字符,但字母,数字,"
, |
, 和 \
除外。
要删除包含"console"的行:
:g@console@d
如果在全局命令中使用替代命令,则可以有两个不同的定界符:
g@one@s+const+let+g
此处,全局命令将查找包含"one"的所有行。 替换命令将从这些匹配项中将字符串"const"替换为"let"。
默认命令
如果在全局命令中未指定任何命令行命令,会发生什么?
全局命令将使用打印(:p
)命令来打印当前行的文本。如果您运行:
:g/console
它将在屏幕底部打印所有包含"console"的行。
顺便说一下,这是一个有趣的事实。因为全局命令使用的默认命令是p
,所以这使g
语法为:
:g/re/p
g
= 全局命令re
= 正则表达式模式p
= 打印命令
这三个元素连起来拼写为 "grep",与命令行中的grep
相同。但这 不 是巧合。 g/re/p
命令最初来自Ed编辑器(一个行文本编辑器)。 grep
命令的名称来自Ed。
您的计算机可能仍具有Ed编辑器。从终端运行ed
(提示:要退出,请键入q
)。
反转整个缓冲区
要翻转整个文件,请运行:
:g/^/m 0
^
表示行的开始。使用^
匹配所有行,包括空行。
如果只需要反转几行,请将其传递一个范围。要将第5行到第10行之间的行反转,请运行:
:5,10g/^/m 0
要了解有关move命令的更多信息,请查看:h :move
。
汇总所有待办事项
当我编码时,有时我会想到一个随机的绝妙主意。不想失去专注,我通常将它们写在我正在编辑的文件中,例如:
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复制到文件末尾以便于自省,请运行:
:g/TODO/t $
结果:
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
:
:g/TODO/m $
结果:
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都会将删除的行存储在编号寄存器中。如果删除多行,所有编号的寄存器将很快被填满。为了避免这种情况,您可以使用黑洞寄存器("_
) 不 将删除的行存储到寄存器中。
:g/console/d _
通过在d
之后传递_
,Vim不会将删除的行保存到任何寄存器中。
将多条空行减少为一条空行
如果您的文件带有多个空行,如下所示:
const one = 1;
console.log("one: ", one);
const two = 2;
console.log("two: ", two);
const three = 3;
console.log("three: ", three);
您可以快速将多个空行减少为一条空行。运行:
:g/^$/,/./-1j
结果:
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
作用在pattern1
和pattern2
上。
记住上面说的格式,让我们根据:g/pattern1/,/pattern2/command
这个格式分解一下命令:g/^$/,/./-1j
:
/pattern1/
就是/^$/
。它表示一个空行(一个没有任何字符的行)。/pattern2/
就是/./
(用-1作为行修正)。/./
表示一个非空行(一个含有至少1个字符的行)。这里的-1
意思是向上偏移1行。command
就是j
,一个联接命令(:j
)。在这个示例中,该全局命令联接所有给定的行。
顺便说一句,如果您想要将多个空行全部删去,运行下面的命令:
:g/^$/,/./j
或者:
:g/^$/-j
您的文本将会减少为:
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
命令来对一个范围内的行进行排序。例如:
d
b
a
e
c
您可以通过运行:sort
对它们进行排序。如果给它一个范围,它将只对该范围内的行进行排序。例如,:3,5sort
仅在第三和第五行之间排序。
如果您具有以下表达式:
const arrayB = [
"i",
"g",
"h",
"b",
"f",
"d",
"e",
"c",
"a",
]
const arrayA = [
"h",
"b",
"f",
"d",
"e",
"a",
"c",
]
如果需要排序数组中的元素,而不是数组本身,可以运行以下命令:
:g/\[/+1,/\]/-1sort
结果:
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
命令。寻找最适合工作的命令,并编写一个模式串以同时定位多个目标。
既然您已经知道全局命令的功能强大,那么让我们学习如何使用外部命令来增加工具库。