2

我正在尝试编写一个脚本来解析试算表。文件中每一行的布局始终相同,但我在让正则表达式正确匹配时遇到问题。该行的前 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

4 回答 4

1

您的pad_zeros函数实际上是sprintf '%0*d', $optimal_length, $num.

您的while(<FIN>)循环读取 tbal 文件中的所有行,并为该文件中的每一行打印该行是否以十位数字开头,但仅针对输入的第一个帐号(readline 运算符<>实际上是一个迭代器,并且在您阅读后用尽所有行)。解决方案是打开if分支内的文件句柄。

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

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

    open my $fin, "<", $filename or die "Can't open $filename: $!";
    

    where包含失败$!的原因。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,或者Tucker, Charles III.我决定匹配“任何不以空格字符结尾的非数字字符串”。

于 2013-04-10T19:01:20.480 回答
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 会自动死掉(并且通常会带有一个很好的错误消息)。

于 2013-04-10T19:21:29.683 回答
0

您的代码没有明显错误。您没有说出“不工作”的意思,但我注意到您正在多次阅读文件以搜索输入。到达文件末尾后,您需要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];
}
于 2013-04-10T19:14:55.140 回答
-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";
        }

任何与所需格式的偏差都会打印“失败”您可以根据需要进行调整。

于 2013-04-10T19:13:17.127 回答