在正则表达式替换中是否有类似计数变量的东西?

33

如果我有很多匹配项,比如在多行模式下,我想用匹配的一部分和一个递增的计数器号码来替换它们。

我想知道是否有任何正则表达式格式支持这种变量。我找不到这样的格式,但好像记得类似的格式存在...

我所说的不是脚本语言,在那些语言中你可以使用回调函数进行替换。这是关于能否在工具中进行这样的操作,例如RegexBuddy、sublime text、gskinner.com/RegExr等,就像你可以使用 \1 或 $1 引用捕获的子字符串一样。


8
有些编程语言允许调用指定的函数,例如JavaScript:var i=0; "foobar".replace(/o/g, function(match) { return match+"("+(i++)+")";}) - Gumbo
你使用的是哪种编程语言? - Gumbo
我使用像http://www.gskinner.com/RegExr/或regexbuddy这样的工具来简化代码块的手动编辑,因此最好使用在这些工具中有效的正则表达式。 - user1115652
2
如果OP假设所有正则表达式都是相同的,或者计数器是一个常见的特性,那么它就是与语言无关的。但实际上并非如此。另外,回调函数并不真正属于正则表达式的范畴,它只是一种高级迭代器。无论如何,您应该发布您正在使用的语言,也许有一个巧妙的两步解决方案。 - Kobi
如果您请求几种可能的目标语言,您可能会得到更多的解决方案。另一方面,这样做也可能会错过一些有趣的解决方案,因为您可能会被卡在一个非常小的最大公因数上。 - tchrist
2个回答

66

关于高级正则表达式的奇技淫巧

好的,我将从简单到复杂地讲解。享受吧!

s///e 简单解决方案

考虑以下内容:

#!/usr/bin/perl

$_ = <<"End_of_G&S";
    This particularly rapid,
        unintelligible patter
    isn't generally heard,
        and if it is it doesn't matter!
End_of_G&S

my $count = 0;

那么这个:

s{
    \b ( [\w']+ ) \b
}{
    sprintf "(%s)[%d]", $1, ++$count;
}gsex;

生成这个

(This)[1] (particularly)[2] (rapid)[3],
    (unintelligible)[4] (patter)[5]
(isn't)[6] (generally)[7] (heard)[8], 
    (and)[9] (if)[10] (it)[11] (is)[12] (it)[13] (doesn't)[14] (matter)[15]!

匿名数组解决方案中的插值代码

相比之下:

s/\b([\w']+)\b/#@{[++$count]}=$1/g;

生成以下内容:

#1=This #2=particularly #3=rapid,
    #4=unintelligible #5=patter
#6=isn't #7=generally #8=heard, 
    #9=and #10=if #11=it #12=is #13=it #14=doesn't #15=matter!

将代码放在LHS而不是RHS的解决方案

这将把增量放在匹配本身内部:

s/ \b ( [\w']+ ) \b (?{ $count++ }) /#$count=$1/gx;

这是什么意思?请提供更多上下文信息。
#1=This #2=particularly #3=rapid,
    #4=unintelligible #5=patter
#6=isn't #7=generally #8=heard, 
    #9=and #10=if #11=it #12=is #13=it #14=doesn't #15=matter!

解决口吃问题的方案

这个

s{ \b ( [\w'] + ) \b             }
 { join " " => ($1) x ++$count   }gsex;

生成这个美妙的答案:
This particularly particularly rapid rapid rapid,
    unintelligible unintelligible unintelligible unintelligible patter patter patter patter patter
isn't isn't isn't isn't isn't isn't generally generally generally generally generally generally generally heard heard heard heard heard heard heard heard, 
    and and and and and and and and and if if if if if if if if if if it it it it it it it it it it it is is is is is is is is is is is is it it it it it it it it it it it it it doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't doesn't matter matter matter matter matter matter matter matter matter matter matter matter matter matter matter!

探索边界

有更强大的方法来处理复数所有格(之前的方法不适用),但我怀疑您的问题在于让 ++$count 起作用,而不是涉及 \b 行为的微妙差别。

我真的希望人们理解 \b 不是他们想象中的那样。他们总是认为它意味着那里有空格或字符串的边缘。他们从未想过它作为 \w\W\W\w 的转换。

# same as using a \b before:
(?(?=\w) (?<!\w)  | (?<!\W) )

# same as using a \b after:
(?(?<=\w) (?!\w)  | (?!\W)  )

如您所见,它的条件性取决于它所接触的内容。这就是(?(COND)THEN|ELSE)子句的作用。

当涉及到以下内容时,这会成为一个问题:

$_ = qq('Tis Paul's parents' summer-house, isn't it?\n);
my $count = 0;

s{
    (?(?=[\-\w']) (?<![\-\w'])  | (?<![^\-\w']) )
    ( [\-\w'] + )
    (?(?<=[\-\w']) (?![\-\w'])  | (?![^\-\w'])  )
}{
    sprintf "(%s)[%d]", $1, ++$count
}gsex;

print;

正确打印的代码

('Tis)[1] (Paul's)[2] (parents')[3] (summer-house)[4], (isn't)[5] (it)[6]?

担心Unicode编码

上世纪60年代的ASCII编码已经过时了50年。就像每当你看到有人写[a-z]时,它几乎总是错误的一样,事实证明,破折号和引号在模式中也不应该显示为字面值。顺便说一下,你可能不想使用\w,因为它还包括数字和下划线,而不仅仅是字母。

想象一下这个字符串:

$_ = qq(\x{2019}Tis Ren\x{E9}e\x{2019}s great\x{2010}grandparents\x{2019} summer\x{2010}house, isn\x{2019}t it?\n);

你可以通过使用use utf8来将其表示为文字:

use utf8;
$_ = qq(’Tis Renée’s great‐grandparents’ summer‐house, isn’t it?\n);

这次我会以不同的方式进行模式,将术语的定义与其执行分开,以尝试使其更易读且易于维护。
#!/usr/bin/perl -l
use 5.10.0;
use utf8;
use open qw< :std :utf8 >;
use strict;
use warnings qw< FATAL all >;
use autodie;

$_ = q(’Tis Renée’s great‐grandparents’ summer‐house, isn’t it?);

my $count = 0;

s{ (?<WORD> (?&full_word)  )

   # the rest is just definition
   (?(DEFINE)

     (?<word_char>   [\p{Alphabetic}\p{Quotation_Mark}] )

     (?<full_word>

             # next line won't compile cause
             # fears variable-width lookbehind
             ####  (?<! (?&word_char) )   )
             # so must inline it

         (?<! [\p{Alphabetic}\p{Quotation_Mark}] )

         (?&word_char)
         (?:
             \p{Dash}
           | (?&word_char)
         ) *

         (?!  (?&word_char) )
     )

   )   # end DEFINE declaration block

}{
    sprintf "(%s)[%d]", $+{WORD}, ++$count;
}gsex;

print;

当运行该代码时,会产生以下结果:

(’Tis)[1] (Renée’s)[2] (great‐grandparents’)[3] (summer‐house)[4], (isn’t)[5] (it)[6]?

好的,那可能是关于复杂正则表达式的文本,但你难道不高兴问吗?☺


感谢您的深入研究。如果我有意使用Perl,我一定会仔细研究它。不过,我相信任何脚本语言都可以做到这一点,所以如果我最终要为此编写脚本,我可能会坚持使用Python,因为我已经了解Python了。 - user1115652
4
你不能在Python中进行一些操作,因为Python不支持Unicode属性、定义块和使用命名缓冲区作为子程序。如果你使用Unicode文本和正则表达式,如果不使用Perl或真正的 PCRE,你就必须做出严格的妥协。请参阅unipropsunichars,了解你在Unicode属性支持方面可能会错过什么。 - tchrist

1

就我所知,在普通的正则表达式中没有这个功能。

另一方面,有几个工具将其作为扩展提供,例如grepWin。在该工具的帮助文档中(按下F1):

grepWin help regarding replacement placeholders

在内部,它使用Boost的Perl正则表达式引擎,但${count}在其中实现的(与其他扩展一样)。


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