5

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

<?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

我究竟做错了什么?

4

5 回答 5

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;
    }
}
于 2012-05-11T22:34:29.420 回答
2

您只需要将您的第一个捕获组放回您的替换组。像这样的东西:

s/<\?php(.*)<\?php(.*?)search_term(.*?)\?>/<\?php$1HELLO/ims
于 2012-05-11T21:57:35.200 回答
2
$string =~ s/<\?php(?:(?!\?>|search_term).)*search_term.*?\?>/HELLO/isg;

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

关键点是,就像@RobertYoung 的解决方案一样,它在?>搜索search_term. 通过不匹配search_term,它消除了回溯,从而使搜索更有效。取决于可能无关紧要的源字符串的大小,但它也不会明显影响性能。

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

于 2012-05-11T23:22:11.303 回答
1
s/(.*)<\?php.*?search_term.*?\?>/${1}HELLO/ims;

在您的正则表达式中,正则表达式引擎试图找到与您的目标表达式匹配的子字符串最早出现的位置,并在 first<?php和 second之间找到它?>

通过放在(.*)正则表达式的开头,您可以欺骗正则表达式引擎进入字符串的末尾(因为.*匹配整个字符串),然后回溯到可以找到字符串“ <?php”的位置。这样,生成的匹配就不会包含<?php不必要的令牌。

于 2012-05-11T22:20:05.073 回答
0

您正在使用贪婪的吝啬匹配,但仍然可以匹配太多。

匹配的重复很好地perlretut描述了它。

我有时会使用否定匹配来提供帮助,但我认为这不会有帮助。例如:

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

以确保我的角色不匹配。

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

于 2012-05-11T21:58:25.323 回答