在vim中,:g和:%s命令有什么区别?

52

今天我开始使用vim。我对:g:%s命令感到困惑。那么,:g:%s命令有什么区别呢?


“:%s”是应用于整个文档的替换命令,而不是像“:g”一样的全局公共前缀。 - Jean-Karim Bockstael
2
@Jean-KarimBockstael 我认为你把 :g[lobal] 命令和选项和变量的 g:[name] 前缀混淆了。 - Ingo Karkat
如果你今天开始使用vim,我强烈推荐你使用vim-tutor。你可以在vim中直接找到相关信息,使用:help tutor命令即可。 - mMontu
你也可以从内置的vim帮助系统中受益匪浅。从:help开始,有很多信息可供查阅。在|usr_10.txt| Making big changes章节中,进一步解释了:s:g命令。 - mMontu
3个回答

75

:g 是全称 global 的缩写,它可以对所有符合正则表达式的行执行命令:

:g/LinesThatMatchThisRegex/ExecuteThisCommand

例子:

:g/hello/d

这将删除包含"hello"的所有行 (d)。

另一方面,:%s 只是在整个文件中执行搜索(使用正则表达式)和替换:

:%s/hello/world/g
g 在结尾处表示 global 或者 greedy(这有争议),所以它将替换行中的所有出现,而不仅仅是每行一个。如果你想要手动确认每个替换,可以使用 c 标志(:%s/hello/world/gc)。
这个命令将把所有的 hello 替换为 world:g:%s 命令都支持正则表达式。 s 命令表示 substitute% 表示整个缓冲区。因此,%s 表示在整个缓冲区内进行替换。你也可以指定行范围:
:10,15s/hello/world/g

这将仅在第10至15行(包括第10和第15行)上执行之前所看到的搜索和替换操作。


2
:g 命令只在当前缓冲区执行,与 :s 命令相同。它们之间唯一的区别在于默认范围::s 命令等同于 :.s,而 :g 命令等同于 :%g。因此,:%s/pat/subs:%g/pat/ cmd(或 :g/...)将作用于缓冲区中所有匹配模式的行,而 :.s/pat/subs(或 :s/...)和 :.g/pat/ cmd 将作用于当前行,如果它匹配模式。 - mMontu
2
g 在末尾(//g)更有可能是代表正则表达式中常用的 贪婪(greedy)的缩写,包括在原始的 PCRE API 中也是如此。它基本上表示“尽可能多地匹配多个结果”。尝试编辑但没有成功,所以只能用注释了。 - miyalys
一些信息:c标志代表确认 - logbasex
实践中的另一个区别是:使用 :g/pattern/d 删除字符串将破坏您的编号寄存器,将它们向下推。而像 :%s/pattern//g 这样替换为空则不会这样做。 - paradroid
:g/{pattern}/d _ 。在 :d 后面加上下划线可以避免覆盖寄存器。而且速度更快。请参阅 :h :g - undefined

21

它们是不同的。

:g 可以为匹配的行执行命令,其中之一就是 :s。也就是说,你可以结合使用 :g 和 s

:%s 在整个缓冲区中进行搜索和替换,虽然它也可以使用表达式做一些其他事情,但不如 :g 直截了当。

例如:

:g/foo/s/bar/blah/g   

这将对包含foo的行进行bar->blah替换。使用:s,我们可以:

这将对包含foo的行进行bar->blah替换。使用:s,我们可以:

:%s/foo/\=substitute(getline('.'), 'bar','blah','g')

所以,:g 更容易。

因此,如果你正在处理替换任务,通常应该首先考虑使用:s。如果你想做类似于对所有匹配 xxx 的行,我想删除/合并/缩进/......的操作,:g 可能会对你有帮助。


12

回顾:

vi或vim中的“:”模式(如ex-mode)命令采用以下形式:

[Address-specifier] [command] [command-specifics] [cmd-modifiers]

地址可以是单行地址(ex-mode 操作“行”),也可以是一个行范围。

例如,在“p”命令中非常简单的一个命令,它将打印被寻址的行。

:1p    - will print line 1.
:5p    - will print line 5.
:1,5p  - will print lines 1 through 5.  1,5 is an address range.
:7,+3p  - will print lines 7 through 10 (7,7+3=10).  A relative range.

在地址空间中有一些简写方式。$和%是最常用的。

$ means "last line in the file".   Thus the expression:
1,$p   - will print all lines, 1 to the LAST-line in the file.

表达式1,$在IT技术中被广泛使用(例如将以下命令应用于文件中的所有行),因此它甚至拥有一个更短的简写符号%。%的意思是"1,$"。

%p  - will print all lines, 1 to the LAST-line in the file, just like 1,$

还有一个特殊的“全局”命令,其作用是提供一组地址前缀,这组地址前缀不一定是线性行范围,而是由正则表达式匹配确定的。":g / regex /"前缀适合ex命令格式的“地址说明符”部分(而不是其后面的命令部分)。它允许指定一组由正则表达式而不是“行号”或“行范围”匹配的行。匹配应用于正则表达式出现在行中,然后该行包含在命令将应用于其中的行列表中。

应用 :1,$s vs %s vs :g/./s

以以下文件为例:

1: 1
2: 1 2 3 4 5 6 1 2
3: 3 2 1
4: 2 3 1 2

使用全局前缀/地址正则表达式和 "p" 打印命令的此命令:

:g/1 2/p   - will print
2: 1 2 3 4 5 6 1 2
4: 2 3 1 2

第2行和第4行都与正则表达式:g/1 2/匹配,并有效扩展为行号列表,对列表中的每个项目应用以下命令。(类似于此命令):
:2p 4p

替换命令允许用其他文本替换与正则表达式匹配的字段。如果我们在示例文件的第2行应用替换命令,我们可以看到其效果。

1: ....
2: 1 2 3 4 5 6 1 2
3: ....

命令:

:2s/1 2/2 1/   will change line 2 to be instead:  2: 2 1 3 4 5 6 1 2

它仅更改模式“1 2”的第一个实例为“2 1”。 如果我们使用“u”撤消此命令,则可以修改后再次运行该命令。 我们可以在命令上使用“p”修饰符,在“替换”中不会有太多作用。 它应用更改,但也在屏幕底部打印已应用的更改(在本例中有些多余)。

:2s/1 2/2 1/p
u (to undo), and then we try it again.
我们可以使用 "c" 修改器来请求确认。
:2s/1 2/2 1/c   The "confirm" modifier for the substitute asks for confirmation on each change.

取消上一步操作(撤销):u。

“全局”修饰符(不是全局地址/正则表达式地址操作符)可以使替换命令对一行执行多个替换。

:2s/1 2/2 1/g    - The "g" here is a modifier to the "s" substitute command.

这意味着在整行上全局执行替换操作。修饰符修改“命令”,而命令适用于一个或多个行,即设置地址字段。在替换命令末尾应用“g”表示:在此行上全局替换,例如每次替换命令的正则表达式出现时,都执行替换。

2: 2 1 3 4 5 6 2 1     - Here, both the first and second instance are substituted.

如果替换命令无法找到匹配的正则表达式,则不执行任何操作。这意味着它可以应用于一系列行,并且只对至少有一个与替换命令正则表达式匹配的行产生影响。

1,4 s/1 2/2 1/
1,$ s/1 2/2 1/
%s/1 2/2 1/

都是等价的,它们将使用替代命令正则匹配模式来代替第一个出现的匹配项,并用替代模式进行替换。

1: 1
2: 1 2 3 4 5 6 1 2
3: 3 2 1
4: 2 3 1 2

变成:

1: 1
2: 2 1 3 4 5 6 1 2
3: 3 2 1
4: 2 3 2 1

在末尾添加 "g" 后变为:

 :%s/1 2/2 1/g
1: 1
2: 2 1 3 4 5 6 2 1
3: 3 2 1
4: 2 3 2 1

g:/regex 前缀

:g/regex/ 地址说明符适用于其后的任何命令,并且该命令可以包括替换命令,包括 "g" 修饰符。

:g/3 4/s/1 2/2 1/g

这个命令的作用是说:"全局匹配符合正则表达式 /3 4/ 的行",然后执行命令。

:s/1 2/2 1/g.    

只有第二行包含正则表达式 /3 4/,所以只有第二行匹配。 因此在此文件上:

:g/3 4/s/1 2/2 1/g is equivalent to:
:2s/1 2/3 4/g, which substitutes all occurrences of 1 2 with 2 1.

1: 1
2: 1 2 3 4 5 6 1 2
3: 3 2 1
4: 2 3 1 2

变成:

1: 1
2: 2 1 3 4 5 6 2 1
3: 3 2 1
4: 2 3 1 2

注意:第 4 行未更改,因为它在地址指示符匹配行中没有模式"3 4"。

:g/regex-line-match/s/match-regex-substitute/sub-pattern/g 

:%s/match-regex-substitute/sub-pattern/g

这两行通常在作用上可以等效。但它们也经常无法等效。等效性取决于正则表达式模式和它们的匹配,因为当一行没有匹配的匹配-正则表达式-替换匹配模式时,“替换”不会执行任何操作。

% = 1,$ which matches all lines, and then applies the substitute pattern.
:g/./   would match every line, if prefixed.

如果全局/正则表达式前缀的正则表达式模式与替换的匹配模式相同,则会有很多额外的输入,但它将限制替换命令仅适用于与全局/正则表达式匹配的行。 如果全局/正则表达式表达式确实匹配每一行,例如:g/^.$/,那么全局行将产生与%相同的效果。(因为%将匹配所有行,而:g/^。 $ /将匹配全部行,所以在基本情况下“s”将执行相同的操作。当使用更典型的正则表达式(匹配某些特定字符串)时,:g / regex /前缀将不同于%。该命令“s”仅适用于首先与g:/ refex /前缀匹配的行,而不是应用于所有行1,$。然后,替换将尝试成功地应用其自己的“每行”匹配模式(和替换),或者在给定的行上找不到匹配并且什么也不做。

全局/正则表达式前缀有趣的地方在于,当全局/正则表达式前缀正则表达式与替换匹配正则表达式模式不同时,您需要首先应用全局/正则表达式(以确定将针对哪些行),然后应用替换命令中的“匹配-替换-正则表达式”模式(可以不同)。 正如我们上面的示例所示,我们使用了“3 4”的全局/正则表达式前缀,以及应用于其次的“1 2”的替换匹配正则表达式模式。

非常高级:

虽然全局/正则表达式本质上是构建要应用命令的行列表,但构建该列表的方式与1,$或其他固定范围指定符不同。 固定指定符在键入:[address]command时“一次性”计算。 另一方面,全局/正则表达式命令在每个子命令的单个应用后重新计算其行目标。

我们将使用“join”命令来说明差异。

1: 1
2: 1 2 3 4 5 6 1 2
3: 3 2 1
4: 2 3 1 2

如果我使用范围语法指定一系列要应用“join”命令的命令,例如::1,$j(或者:%j),则会产生以下结果:

1: 1 1 2 3 4 5 6 1 2 3 2 1 2 3 1 2

这种情况是由于1,$在开始时选择了第1行和第4行,然后对每个选定的行应用“j”,将范围内的所有行合并。

但是,如果我们改为使用全局前缀操作符(匹配所有行),应用程序就不同:

:g/./j

这将呈现:

 1: 1 1 2 3 4 5 6 1 2
 2: 3 2 1 2 3 1 2

因为在两种语法中,“命令”何时以及如何应用的方式不同,所以会出现差异。在第一种“:%j”语法中,所有行都会先计算出来,然后对每一行应用“j”命令。

而使用全局/正则表达式语法时,则是基于“随时随地”和“从当前位置”应用行和命令。因此,“:g/./j”命令首先匹配LINE1,然后运行“j”将行1和行2组合成新的第一行。接着它会前进到文件的“下一行”(即新文件new-2),匹配该行(/./匹配所有内容)并将“j”应用于new-2(原始的第三行)和new-3(原始的第四行)以创建新的new-new-2 = 3+4。然后再前进到“新的新文件”的下一行,这是第3行(但没有新的new-new-3,所以它停止了)。结果为:

 1: 1 1 2 3 4 5 6 1 2
 2: 3 2 1 2 3 1 2

关键区别在于应用命令实例后,全局正则表达式搜索会在应用命令后的文件中的“下一行”上继续。

正如早期的帖子总结得那样(但假设读者拥有更多知识):

:g/first-search-pattern/s/match-pattern/substitute-pattern/g  or /gc for confirm.

摘要:

这些模式可以是不同的,末尾的g或gc可以存在(每行上的所有出现次数,带有或不带有确认),也可以省略(仅限每行的第一次出现)。在编写时:

:%s/pattern/replace/g    is common, the following is nearly equivalent:
:g/./s/pattern/replace/g  (less common, but basically the with "substitute" command).

"$ 表示“文件中的最后一行”,这对我来说很有趣,因为vim通常使用“$”表示行末,“G”表示文件末尾。我想知道为什么在这种地址范围的情况下会有所不同。" - Kalcifer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接