在Perl中使用正则表达式的麻烦

时间:2013-04-10 18:36:50

标签: regex perl text-parsing string-parsing

我正在尝试编写一个脚本来解析试算表。文件中每一行的布局总是相同的,但是我的问题是让我的正则表达式正确匹配。该行的前10个字符始终是帐号。这是一个例子:

0000000099 S000 Doe, John  00 1,243.22  01/01/1901 

我试图将这些列中的每一个捕获到一个单独的变量,但我的表达式不起作用。

这是我到目前为止所拥有的。

#!/usr/bin/perl -w
use strict;

my $filename = "S:\\TELLERS\\GalaxyDown\\tbal";
my $answer   = undef;
open(FIN, $filename) || die "File not found";

do {
    print "Enter an account number: ";
    chomp(my $acctNum = <STDIN>);

    if ($acctNum =~ /\d{1,10}/) {
        $acctNum = pad_zeros($acctNum);

        #print "$acctNum\n";    #test to make sure the padding extends the account
                                #number to 10 digits - comment out after verification

        while (<FIN>) {

            #print "$_\n";

            if (m/(^[0-9]{10}/) {
                print "Passed\n";
            }
            else {
                print "Failed\n";
            }
        }

    }
    else {
        print "Invalid account number. Please try again.\n";
    }
    print "Would you like to view another account balance? (yes/no): ";
    chomp($answer = lc <STDIN>);

} while ($answer ne "no");

sub pad_zeros {
    my $optimal_length = 10;
    my $num            = shift;
    $num =~ s/^(\d+)$/("0"x($optimal_length-length$1)).$1/e;
    return $num;
}

任何帮助都将不胜感激。

4 个答案:

答案 0 :(得分:1)

pad_zeros函数实际上是sprintf '%0*d', $optimal_length, $num的缩写形式。

您的while(<FIN>)循环读取tbal文件中的所有行,并打印该文件中的每一行,无论该行是以十位数开头,还是仅输入第一个帐号(readline运算符{{1实际上是一个迭代器,在读完所有行后都会耗尽)。解决方案是在<>分支内打开文件句柄。

还有一些其他方面可以改进:

  • 您不需要使用if初始化标量变量:这已经是默认值了。
  • 要打开文件句柄,您应该(1)对该文件句柄使用普通变量,(2)使用undef的三参数形式:

    open

    其中open my $fin, "<", $filename or die "Can't open $filename: $!"; 包含$!失败的原因。指定显式模式open会使一些极端情况更加安全。

  • 反斜杠路径很难看,但是Windows处理正常斜线就好了<

要将一条线分割成多个字段,您必须考虑确切的格式:每个字段是否由公共分隔符分隔,例如空格?在那种情况下,

S:/TELLERS/...

会做到这一点。将my @fields = split " ", $line; 更改为正则表达式,确定不同分隔符的分隔符(制表符,逗号等)。

但是,您的格式看起来并不那么简单,因为姓氏后面的逗号可能不是姓氏字段数据的一部分(?)

这样的正则表达式
" "

可能会更好,但这取决于您拥有的完全格式。

匹配名称很难,因为一些人可能有多个名字。考虑条目my $regex = qr{\A \s* ([0-9]{10}) \s+ (S[0-9]{3}) \s+ ([^,]+), # the surname \s+ ([^0-9]+(?<!\s)) # other names \s+ ([0-9]{2}) \s+ ([0-9,]+\.[0-9]{2}) \s+ ([0-9]{2}) / ([0-9]{2}) / ([0-9]{4}) \s*\z }x; my @fields = $line =~ $regex; Gogh, Vincent van我决定匹配“任何不以空格字符结尾的非数字字符串”。

答案 1 :(得分:1)

我没有得到任何积分。 Amon几乎已经钉了它,给了你需要知道的一切,包括一些很好的建议。

您说您的帐户行如下:

0000000099 S000 Doe, John  00 1,243.22  01/01/1901 

问题是空格可以用作名称的一部分。 Mary Jane Von Corona 有四个空格。但是,它是名字, Mary Jane ,姓氏 Von Corona 。我如何知道名称的分割位置?

最好的方法是使用固定长度字段,或使用不在文件中的分隔符。

0000000099|S000|Doe|John|00|1,243.22|01/01/1901

在这里,我使用|作为字段分隔符。我能做到这一点:

my ( $account,   $something,   $something2,
     $last,      $first,       $something3,
     $balance,   $date)                       = split /\|/, $line;

这是在|上一次性分割整行。

如果字段具有固定宽度,我可以使用substr函数拉开此行中的各个字段:

my $account = substr( $line, 0, 10 );   #First 10 characters is always the account number

我还建议使用autodie。这样,您无需测试各种内容,例如文件是否已成功打开。当这样的事情发生时,Perl会自动死掉(并且通常会有一个很好的错误信息)。

答案 2 :(得分:0)

您的代码没有任何明显错误。您没有说出“not working”的含义,但我注意到您正在多次读取该文件以搜索输入。一旦到达文件末尾,您需要seek再次开始或重新打开文件。

以下是一些建议

  • 不要使用-w命令行限定符。 use warnings要好得多

  • 使用单引号删除包含反斜杠的字符串。然后他们不需要转义,除非它们中不止一个或它们出现在字符串的末尾

  • 如果您使用snake_case代替CamelCase作为本地标识符,您会让很多经验丰富的Perl程序员更开心

  • 目前的最佳做法是使用词法文件句柄和open的三参数形式。你应该将$!放入die字符串中,这样你就可以为什么打开失败

  • 您在输入中检查/\d{1,10}/,它会测试字符串是否包含一串数字。你可能意味着/^\d{1,10}$/

  • sub pad_zeroes最好写成sprintf '%0*d', $optimal_length, $_[0]

这是建议的重写。我更改了代码以检查输入文本指定的帐户是否已被读取,这可能是您的意图。

注意对于输入的每个新帐号,对文件进行顺序搜索效率极低,仅适用于小型数据文件或一次性程序。我建议你使用Tie::File以及一个散列,该散列指示要读取的绑定数组的哪个元素以访问给定的帐号。

注意您的文件似乎使用固定宽度字段,即字段始终以行中相同的字符位置开头和结尾。如果是这样,那么您应该使用substrunpack而不是使用正则表达式来处理数据。更好的是,模块Parse::FixedLength允许您只需指定每个字段的长度,并为您完成剩余的工作。

#!/usr/bin/perl

use strict;
use warnings;

my $filename = 'S:\TELLERS\GalaxyDown\tbal';
my $answer;

do {
    print "Enter an account number: ";
    chomp(my $acct_num = <STDIN>);

    if ($acct_num =~ /^\d{1,10}$/) {

        $acct_num = pad_zeroes($acct_num);

        #print "$acct_num\n";    #test to make sure the padding extends the account
                                 #number to 10 digits - comment out after verification

        open(my $fin, '<', $filename) || die "File not found: $!";
        while (<$fin>) {
            if (/^$acct_num/) {
              print "Passed\n";
            }
        }
    }
    else {
        print "Invalid account number. Please try again.\n";
    }
    print "Would you like to view another account balance? (yes/no): ";
    chomp($answer = lc <STDIN>);

} until $answer eq 'no';


sub pad_zeroes {
    my $optimal_length = 10;
    sprintf '%0*d', $optimal_length, $_[0];
}

答案 3 :(得分:-1)

如果您想查看整行,可以使用以下内容:

  while(<FIN>){

        if( @a = (m/^\s*(\d{1,10})\s+(S\d+)\s+(\w+)\s*,\s*(\w+)\s+(\d\d)\s+(\S+)\s+(\d\d?\/\d\d?\/(?:\d\d)\d\d)\s*/) ) {
            $a[0] = sprintf "%010d", $a[0];
            print "Account number:  $a[0]";
            print "Account series:  $a[1]";
            print "Account owner:   $a[3] $a[2]";
            print "Account type:    $a[4]";
            print "Account balance: $a[5]";
            print "Account date:    $a[6]";
        } else {
            print "Failed\n";
        }

任何与所需格式的偏差都将打印出来&#34;失败&#34; 您可以根据自己的需要进行调整。