Vim正则表达式捕获组 [bau -> byau : ceu -> cyeu]

198

我有一个单词列表:

bau
ceu
diu
fou
gau

我想把那个列表变成:

byau
cyeu
dyiu
fyou
gyau

我尝试运行了以下命令,但未成功:

:%s/(\w)(\w\w)/\1y\2/g

鉴于这不起作用,我需要改变什么才能让Vim中的正则表达式捕获组起作用?


可能是在Vim中使用正则表达式匹配包含任意行的表达式和http://stackoverflow.com/questions/18627893/vim-match-errors-out-with-regular-expression-ffelrf的重复问题。 - Ingo Karkat
4
这段话是关于编辑器Vim中的命令,原文为:“It's a little bit off-topic so I put it here as a comment but… I'd do :%norm ay<CR>.”。翻译后为:“这句话与主题有些偏离,所以我把它放在评论中,但我会执行“:%norm ay<CR>”命令。” - romainl
5
如果您的情况与描述的完全相同,那么可以选择以下操作:使用 l 键移动到第二列,在 Visual Block 模式下按 Ctrl+v 进入,使用 Shift+g 然后 l 标记整个列,然后使用 Shift+i 进入插入模式并输入 'y'。完成后需要按 Esc 退出插入模式,总共只需 7 个按键操作。这里没有将其发布为答案,因为它实际上与捕获组无关(当我搜索到这个问题时,我正在寻找与捕获组有关的内容)。:) - LAFK says Reinstate Monica
5个回答

318

修复此问题的一种方法是确保该模式被 转义 括号所包围:

:%s/\(\w\)\(\w\w\)/\1y\2/g

稍微更短些(并且更加神奇)的方法是使用\v,在它后面的模式中,除了'0'-'9''a'-'z''A'-'Z''_'以外的所有ASCII字符都具有特殊含义:

:%s/\v(\w)(\w\w)/\1y\2/g

请参见:


69

您也可以使用这个较短的模式:

:%s/^./&y
  • %s 将模式应用于整个文件。
  • ^. 匹配行的第一个字符。
  • &y 在模式后添加 y

2
令人惊奇的是,经过十多年的vim使用和相当多的专业知识后,我仍然能学到新技巧,比如使用"&"来添加而不是替换。谢谢。 - Kiteloopdesign
1
@Kiteloopdesign & 实际上只是 \0 的另一个名称,它是包含整个匹配序列的捕获组。 - cuddlebugCuller

57

如果你不想使用反斜杠来转义捕获组(这是你错过的部分),则在前面添加\v,将Vim的正则表达式引擎切换为非常魔幻模式:

:%s/\v(\w)(\w\w)/\1y\2/g

Ingo,抱歉在错误的地方提出问题:这在 :exmode 中可以正常工作;是否有办法在 gvim 查找/替换对话框中实现? - JJoao
3
不,查找/替换框仅适用于文字搜索和替换。无论如何,您都不应该使用它;这只是为记事本用户提供的训练轮。 - Ingo Karkat
Ingo,谢谢你(不是为我说的:我对exmode感到满意,而是为字典项目中的语言学合作者说的):它几乎可以工作——使用\v...正则表达式可以很好地运行;在替换字符串中,&可以工作,但\ 被保护了(\1\r丢失)。 - JJoao
@JJoao:是的,这也是我在使用它时发现的。我仍然怀疑是否使用没有Ex模式的Vim是一个好主意,但是您可以通过inputdialog()和一些Vimscript轻松构建自己的搜索和替换对话框(内部由:s驱动)。 - Ingo Karkat
Ingo:再次感谢您,我同意您持怀疑态度的观点。Inputdialg +:s + vimscript可能是gvim的查找替换方式。对我来说,“\1 \r”处理是gvim的一个错误。我将尝试在一些vim特定的列表中发布它。同时,我将尝试使用自己的vimscript-inputdialog。 - JJoao

17
你还需要转义分组括号:
:%s/\(\w\)\(\w\w\)/\1y\2/g

那就万事大吉了。

3
从Sublime Text 3来看,这太糟糕了。为什么语法会是这样?转义那些不是字面上的普通文本字符毫无意义。 - Unknow0059
@Unknow0059 在这种情况下,括号不是字面文本。它们是元字符,用于分隔要保存到替换表达式中的组。在表达式中放置一个非转义括号将匹配字面字符,正如人们所期望的那样(这就是让 OP 困惑的原因)。 - Azure Heights
1
我是一个经常使用vim的用户,我也认为这很糟糕。@Unknow0059 - icedwater
1
@Unknow0059 因为vim比我们现在普遍使用的正则表达式语法要古老。大多数使用vim的人只是使用其他答案中描述的\v版本,而不是在他们的正则表达式中逐个转义每一个小东西。 - CoffeeTableEspresso

6
在 Vim 中,对于所选内容,下列操作会:
:'<,'>s/^\(\w\+ - \w\+\).*/\1/

或者
:'<,'>s/\v^(\w+ - \w+).*/\1/

parses

Space - Commercial - Boeing

被解析为

Space - Commercial

同样地,

apple - banana - cake - donuts - eggs

被解析为

apple - banana

说明

  • ^:匹配行首
  • \-用于转义(, +, ),按第一个正则表达式(接受的答案)-- 或在正则表达式前加上\v (@ingo-karkat的答案)
  • \w\+ 查找单词(\w只查找第一个字符):在此示例中,我搜索以一个单词后跟-再跟随另一个单词的形式
  • .* 在捕获组之后需要加上,以查找/匹配/排除剩余的文本

补充说明。这有点偏题,但我建议Vim不适合执行更复杂的正则表达式/捕获操作。[我正在执行类似以下内容的操作,这也是我找到这个主题的方式。]

在这些情况下,最好将行转储到文本文件中,并进行“就地”编辑。

sed -i ...

或者重定向到一个文件中:

sed ... > out.txt

在终端(或BASH脚本,...)中使用:


echo 'Space Sciences - Private Industry - Boeing' | sed -r 's/^((\w+ ){1,2}- (\w+ ){1,2}).*/\1/'

Space Sciences - Private Industry 

cat in.txt

Space Sciences - Private Industry - Boeing

sed -r 's/^((\w+ ){1,2}- (\w+ ){1,2}).*/\1/' ~/in.txt > ~/out.txt

cat ~/out.txt 

Space Sciences - Private Industry

## Caution: if you forget the > redirect, you'll edit your source.
## Subsequent > redirects also overwrite the output; use >> to append
## subsequent iterations to the output (preserving the previous output).
 
## To edit "in place" (`-i` argument/flag):

sed -i -r 's/^((\w+ ){1,2}- (\w+ ){1,2}).*/\1/' ~/in.txt

cat in.txt

Space Sciences - Private Industry 

sed -r 's/^((\w+ ){1,2}- (\w+ ){1,2}).*/\1/'

(注意 {1,2} 允许查找一个词或两个词的重复 -- 参见 https://www.gnu.org/software/sed/manual/html_node/Regular-Expressions.html。)

在这里,由于我的短语是由 - 分隔的,我可以简单地调整这些参数来得到我想要的结果。


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