将最接近的字符串与搜索词匹配(perl regex)

时间:2012-05-11 21:51:50

标签: regex perl replace

基本上,我要做的是搜索一个相当大的PHP文件,并用其他代码替换包含字符串“search_term”的任何PHP代码块。即。

<?php
//some stuff
?>
<?php
// some more stuff
$str = "search_term";
// yes...
?>
<?php 
// last stuff
?>

应该成为

<?php
//some stuff
?>
HELLO
<?php 
// last stuff
?>

到目前为止我所得到的是

$string =~ s/<\?php(.*?)search_term(.*?)\?>/HELLO/ims;

这与最近的结束?>正确匹配,但会在第一个<?php开始匹配,而不是最接近字符串search_term的匹配。

我做错了什么?

5 个答案:

答案 0 :(得分:5)

一般来说,我不喜欢使用非贪婪的匹配,因为它通常会导致这样的问题。 Perl查看您的文件,找到第一个'<?php',然后开始查找其余的正则表达式。它会经过第一个'?>'和第二个'<?php',因为它们匹配.*,然后找到search_term和下一个'?>',并且已完成。

非贪婪匹配意味着你有一个正常的表达式,它匹配的东西比你真正想要的更多,并且它将它留给perl来决定返回哪个匹配。最好使用与您想要匹配的正则匹配的正则表达式。在这种情况下,您可以使用((?!\?>).)*代替.*?来获得所需内容((?!\?>)是一个负面的预见断言)

s/<\?php((?!\?>).)*search_term((?!\?>).)*\?>/HELLO/is;

如果您希望多次匹配,则可能需要使用/isg而不是/is

或者,只需将文件拆分为块:

@blocks = split /(\?>)/, $string;
while (@blocks) {
    $block = shift @blocks;
    $sep = shift @blocks;
    if ($block=~/search_term/) {
        print "HELLO";
    } else {
        print $block, $sep;
    }
}

答案 1 :(得分:2)

您只需将第一个捕获组放回替代品中即可。像这样:

s/<\?php(.*)<\?php(.*?)search_term(.*?)\?>/<\?php$1HELLO/ims

答案 2 :(得分:2)

$string =~ s/<\?php(?:(?!\?>|search_term).)*search_term.*?\?>/HELLO/isg;

(?:(?!\?>|search_term).)*一次匹配一个字符, 后确保该字符不是?>search_term的开头。当它停止匹配时,如果字符串中的下一个内容是search_term,它会消耗它以及之后的所有内容直到下一个?>。否则,该尝试将失败,并在下一个<?php开始。

至关重要的是,与@ RobertYoung的解决方案一样,在搜索?>时,不允许匹配search_term。通过不匹配search_term,它消除了回溯,这使得搜索更有效。取决于可能无关紧要的源字符串的大小,但它也不会显着损害性能。

@ Benj的解决方案(目前已发布)不起作用。它会使用您提供的样本字符串产生所需的输出,但这只是偶然的。它只用其中search_term替换 last 代码块,并且(如@mob所述)它完全忽略了第一个代码块的内容。

答案 3 :(得分:1)

s/(.*)<\?php.*?search_term.*?\?>/${1}HELLO/ims;

在正则表达式中,正则表达式引擎尝试查找与目标表达式匹配的子字符串的最早出现,并在第一个<?php和第二个?>之间找到它。

通过将(.*)置于正则表达式的开头,您可以欺骗正则表达式引擎转到字符串的末尾(因为.*匹配整个字符串),然后回溯到它的位置可以找到字符串“<?php”。这样,生成的匹配将不再包含任何<?php令牌。

答案 4 :(得分:0)

你正在使用贪心吝啬匹配,但仍然可以匹配太多。

Matching repetitions in perlretut很好地描述了它。

我有时会使用否定的比赛来帮助,但我认为这不会有所帮助。例如:

s/^[^A]*A/A/

确保我的字符不匹配。

但我通常不会尝试跨越多行,除非必须,否则我不会使用perl。