基本上,我要做的是搜索一个相当大的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
的匹配。
我做错了什么?
答案 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。