我正在尝试编写一个脚本来解析试算表。文件中每一行的布局总是相同的,但是我的问题是让我的正则表达式正确匹配。该行的前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;
}
任何帮助都将不胜感激。
答案 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
会使一些极端情况更加安全。
<
。要将一条线分割成多个字段,您必须考虑确切的格式:每个字段是否由公共分隔符分隔,例如空格?在那种情况下,
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
以及一个散列,该散列指示要读取的绑定数组的哪个元素以访问给定的帐号。
注意您的文件似乎使用固定宽度字段,即字段始终以行中相同的字符位置开头和结尾。如果是这样,那么您应该使用substr
或unpack
而不是使用正则表达式来处理数据。更好的是,模块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; 您可以根据自己的需要进行调整。