为什么我的Perl正则表达式只找到最后一次出现?

时间:2010-03-18 07:19:06

标签: regex perl

我对Perl脚本有以下输入,我希望在每个<table>...</table>结构中首次出现NAME =“...”字符串。

整个文件被读入一个字符串,正则表达式作用于该输入。

但是,正则表达式始终返回最后一次出现的NAME="..."个字符串。任何人都可以解释发生了什么以及如何解决这个问题?

Input file: 
ADSDF
<TABLE>
NAME="ORDERSAA"
line1
line2
NAME="ORDERSA"
line3
NAME="ORDERSAB"
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSB"
line3
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSC"
line3
</TABLE>
<TABLE>
line1
line2
NAME="ORDERSD"
line3
line3
line3
</TABLE>
<TABLE>
line1
line2
NAME="QUOTES2"
line3
NAME="QUOTES3"
NAME="QUOTES4"
line3
NAME="QUOTES5"
line3
</TABLE>
<TABLE>
line1
line2
NAME="QUOTES6"
NAME="QUOTES7"
NAME="QUOTES8"
NAME="QUOTES9"
line3
line3
</TABLE>
<TABLE>
NAME="MyName IsKhan"
</TABLE>

Perl Code从这里开始:

use warnings;
use strict;

my $nameRegExp = '(<table>((NAME="(.+)")|(.*|\n))*</table>)';

sub extractNames($$){
 my ($ifh, $ofh) = @_;
 my $fullFile;
 read ($ifh, $fullFile, 1024);#Hardcoded to read just 1024 bytes.
 while( $fullFile =~ m#$nameRegExp#gi){
  print "found: ".$4."\n";
 }
}

sub main(){
 if( ($#ARGV + 1 )!= 1){
  die("Usage: extractNames infile\n");
 }
 my $infileName = $ARGV[0];
 my $outfileName = $ARGV[1];
 open my $inFile, "<$infileName" or die("Could not open log file $infileName");
 my $outFile;
 #open my $outFile, ">$outfileName" or die("Could not open log file $outfileName");
 extractNames( $inFile, $outFile );
 close( $inFile );
 #close( $outFile );
}

#call 
main();

4 个答案:

答案 0 :(得分:4)

试试这个:

'(?><TABLE>\n+(?:(?!</TABLE>|NAME=).*\n+)*)NAME="([^"]+)"'

(?:.*\n+)*消耗任何不需要的行,而嵌入式预测 - (?!</TABLE>|NAME=) - 使其不会超出第一个NAME字段或TABLE记录的结尾。为了防止有一个没有NAME字段的记录,我将大部分表达式包装在一个原子组中 - (?>...) - 以防止无意义的回溯。

请注意,现在只有一个捕获组。只有当你真的需要捕捉某些东西时才使用它们是一种好习惯;否则,使用非捕获变种:(?:...)


编辑:至于为什么你的正则表达式不起作用,简短的回答就是贪婪。匹配开始标记后,此部分将接管:

((NAME="(.+)")|(.*|\n))*

最外面的parens中的部分可以匹配任何内容:标记,NAME=行,换行符 - 甚至是空行。将其包裹在由贪婪的*控制的组中,现在它匹配所有内容。没有任何内容可以使它在第一个NAME字段停止匹配,甚至在记录结束时停止匹配。

所以它实际上“找到”每个出现的NAME="..."个字符串,但它是在一次匹配尝试中一次性消耗整个输入。对于封闭*的每次迭代,捕获组都会被覆盖;当它完成时,最终的NAME值 - MyName IsKhan - 恰好留在第4组。

我使用负向前瞻来检查贪婪,但你也可以通过使用非贪婪的量词来更直接地做到这一点。这是我的正则表达式看起来如何用一个不情愿的*取代负面的前瞻:

'<TABLE>\n+(?:.*\n+)*?NAME="([^"]+)"'

简单地切换到非贪婪的量词对你的正则表达式没有帮助;你也必须做出一些结构性的改变。

答案 1 :(得分:1)

尝试让你的正则表达式非贪婪:

my $nameRegExp = '(<table>((NAME="(.+?)")|(.*?|\n))*</table>)';

即使上述正则表达式 也会列出文件中的所有NAME行。它将列出每个<TABLE>...</TABLE>块中的一个NAME行(最后一行)。

列出您可以执行的所有NAME行:

my $nameRegExp = 'NAME="(.+?)"';

print $1;

答案 2 :(得分:1)

首先,用正则表达式解析XML是个坏主意。 其次,您需要将正则表达式更改为以下内容:

my $nameRegExp = '(<table>((NAME="(.+)?")|(.*?|\n))*?</table>)';

这样正则表达式变得非贪婪,应该返回第一次出现。

答案 3 :(得分:1)

$/ = '</TABLE>';
while (<>) {
    chomp;
    @F = split "\n";
    $g = 0;
    for ($o = 0; $o <= $#F; $o++) {
        if ($F[$o] =~ /^NAME=/) {
            $F[$o] =~ s/^NAME=//g;
            $v = $F[$o];
            $g = 1;
            last;
        }
    }    
    if ($g) {  print $v."\n"; }
}

输出

$ perl myscript.pl file
"ORDERSAA"
"ORDERSB"
"ORDERSC"
"ORDERSD"
"QUOTES2"
"QUOTES6"
"MyName IsKhan"

它的全部要点:使用</TABLE>作为记录分隔符,使用换行符作为字段分隔符。浏览每个字段并找到NAME=。如果找到,请在=符号后替换并获取字符串。