2011-04-22 41 views
1

我知道了吗?操作员启用“非贪婪”模式,但我遇到了一个问题,我似乎无法绕开。考虑像这样的字符串:最短匹配问题

my $str = '<a>sdkhfdfojABCasjklhd</a><a>klashsdjDEFasl;jjf</a><a>askldhsfGHIasfklhss</a>'; 

那里有开始和结束标记<a></a>,有钥匙ABC,DEF和GHI,但其他一些随机文本包围。例如,我想用<b>TEST</b>替换<a>klashsdjDEFasl;jjf</a>。但是,如果我有这样的事情:

$str =~ s/<a>.*?DEF.*?<\/a>/<b>TEST><\/b>/; 

即使非贪婪操作符*?这并不做我想做的。我知道为什么它没有这样做,因为第一个<a>匹配字符串中的第一个匹配项,并一直匹配到DEF,然后匹配到最接近的关闭</a>。然而,我想要的是尽可能匹配最接近的开头<a>并关闭</a>到“DEF”。所以目前,我得到这个作为结果:

<a>TEST</b><a>askldhsfGHIasfklhss</a> 

凡为我寻找的东西得到这样的结果:

<a>sdkhfdfojABCasjklhd</a><b>TEST</b><a>askldhsfGHIasfklhss</a> 

顺便说一句,我并不是想在这里解析HTML,我知道有模块可以做到这一点,我只是问如何做到这一点。

感谢, 埃里克·塞弗特

回答

6
$str =~ s/(.*)<a>.*?DEF.*?<\/a>/$1<b>TEST><\/b>/; 

的问题是,即使非贪婪匹配,Perl的仍然试图找到开始在字符串中最左边的可能点了比赛。由于.*?可以匹配<a></a>,这意味着它总是会找到第一个<a>就行了。

在开始添加一个贪婪(.*)使其找到最后可能匹配就行了<a>(因为.*首先抓住全行,然后回溯,直到找到匹配)。

一个警告:因为它首先找到最右边的匹配,所以不能在/g修饰符中使用此技巧。任何额外的比赛将在$1之内,并且/g恢复前一场比赛结束的搜索,因此它不会找到它们。相反,你不得不使用像一个循环:

1 while $str =~ s/(.*)<a>.*?DEF.*?<\/a>/$1<b>TEST><\/b>/; 
+0

谢谢,这正是我一直在寻找的。 – 2011-04-22 17:20:01

2

而不是一个点的它说:“匹配任何字符不是:用你真正需要它说“匹配任何字符”</a>”的开头。这转化为这样的事情:

$str =~ s/<a>(?:(?!<\/a>).)*DEF(?:(?!<\/a>).)*<\/a>/<b>TEST><\/b>/; 
+0

@ysth:感谢逃生...... – ridgerunner 2011-04-22 17:15:31

0
#!/usr/bin/perl 
use warnings; 
use strict; 

my $str = '<a>sdkhfdfojABCasjklhd</a><a>klashsdjDEFasl;jjf</a><a>askldhsfGHIasfklhss</a>'; 

my @collections = $str =~ /<a>.*?(ABC|DEF|GHI).*?<\/a>/g; 

print join ", ", @collections; 
+0

你所做的只是改变正则表达式,因此它匹配字符串中出现的所有' ...'。这并不能解决原来的问题,即只匹配其中一组。 – cjm 2011-04-22 17:46:45

+0

啊,你说得对。 @cjm – SymKat 2011-04-22 18:15:38

0
s{ 
    <a> 
    (?: (?! </a>) .)* 
    DEF 
    (?: (?! </a>) .)* 
    </a> 
}{<b>TEST</b>}x; 

基本上,

(?: (?! PAT) .) 

[^CHARS] 

的正则表达式模式,而不是字符等效。