关于正则表达式:我可以在VIM或Perl中的单个正则表达式中替换多个项目吗?

Can I substitute multiple items in a single regular expression in VIM or Perl?

假设我有一条线"快速的棕色狐狸跳过懒惰的狗",我能用一个正则表达式把它改成"缓慢的棕色狐狸跳过充满活力的狗"吗?目前,对于这种情况,我使用两组正则表达式。(在本例中,我使用s/quick/slow/,然后使用s/lazy/energetic/


您可以使用字典在VIM中执行此操作:

1
:%s/quick\|lazy/\={'quick':'slow','lazy':'energetic'}[submatch(0)]/g

这将更改以下文本:

The quick brown fox ran quickly next to the lazy brook.

进入:

The slow brown fox ran slowly next to the energetic brook.

要了解这是如何工作的,请参阅:help sub-replace-expression:help Dictionary。简而言之,

  • \=允许您替换vim表达式的结果。
  • {'quick':'slow', 'lazy':'energetic'}是一个VIM字典(像Perl或Ruby中的散列,或javascript中的对象),使用[]进行查找。
  • submatch(0)是匹配的字符串

这在重构代码时非常有用,比如您想将变量名替换为foobarbaz更改

  • foo→bar
  • bar→baz
  • baz→foo

除非使用临时变量名,否则使用一系列%s///命令是很困难的,但必须确保这些命令不会影响其他任何内容。相反,您可以使用字典一次完成:

1
:%s/\<\%(foo\|bar\|baz\)\>/\={'foo':'bar','bar':'baz','baz':'foo'}[submatch(0)]/g

它改变了这个代码

1
2
3
4
5
6
int foo = 0;
float bar = pow(2.0, (float) foo);
char baz[256] = {};

sprintf(baz,"2^%d = %f
"
, foo, bar);

进入:

1
2
3
4
5
6
int bar = 0;
float baz = pow(2.0, (float) bar);
char foo[256] = {};

sprintf(foo,"2^%d = %f
"
, bar, baz);

如果你发现自己经常这样做,你可能想在你的~/.vimrc中增加以下内容:

1
2
3
4
5
6
7
" Refactor the given lines using a dictionary
"
replacing all occurences of each key in the dictionary with its value
function! Refactor(dict) range
  execute a:firstline . ',' . a:lastline .  's/\C\<\%(' . join(keys(a:dict),'\|'). '\)\>/\='.string(a:dict).'[submatch(0)]/ge'
endfunction

command! -range=% -nargs=1 Refactor :<line1>,<line2>call Refactor()

这允许您使用:Refactor {'frog':'duck', 'duck':'frog'}命令,并且稍微比手动为dict创建regex的重复性更低。


替换的第二部分是双引号字符串,因此可以进行任何正常的插值。这意味着您可以使用捕获的值对哈希进行索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/perl

use strict;
use warnings;


my %replace = (
    quick =>"slow",
    lazy  =>"energetic",
);

my $regex = join"|", keys %replace;
$regex = qr/$regex/;

my $s ="The quick brown fox jumps over the lazy dog";

$s =~ s/($regex)/$replace{$1}/g;

print"$s
"
;


您可以连接VIM替换:

那只敏捷的棕色狐狸在那条懒惰的小溪边跑得很快。

1
:s/quick/slow/|s/lazy/energetic/

那只慢腾腾的棕色狐狸很快地跑到了那条充满活力的小溪边。

这里的优点是你只需要输入一次替代品

RGDS


您可以执行以下操作。

1
:%s/quick\(.*\)lazy/slow\1energetic

诀窍是使用parens来匹配两个单词之间的文本。然后可以使用\1在替换字符串中引用此文本。您还可以使用\2来表示第二个匹配的paren表达式等。这允许您替换多个单词,而不会干扰中间的文本。


在Perl中:

1
s/quick(.*)lazy/slow${1}energetic/;

在VIM中:

1
s/quick\(.*\)lazy/slow\1energetic/;


查斯的回答很好,我唯一要提到的是,如果你在做单词交换,你可能想匹配

b(foo_bar_baz_qux)b 

以避免匹配子字符串。如果你做了大量的换词,你可能会开始发现regexps有点限制,并想做如下的事情:

1
join '', map { exists $subst{$_} ? $subst{$_} : $_ } split /\b/, $string


在Ruby中,有一种简洁的方法可以使用带有块的gsub来完成这项工作:

1
2
3
4
s ="The quick brown fox jumps over the lazy dog"
subs = {'quick' => 'slow', 'lazy' => 'industrious'}
s.gsub(/quick|lazy/) { |match| subs[match] }
# =>"The slow brown fox jumps over the industrious dog"