我正在尝试读取一个分类在某个位置下的用户信息的文件,我想使用用户输入填写一些字段并输出文件,同时保持每个位置下的字段完整,例如 - file
[California]
$;FIrst_Name =
$;Last_Name=
$;Age =
[NewYork]
$;FIrst_Name =
$;Last_Name=
$;Age =
[Washington]
$;FIrst_Name =
$;Last_Name=
$;Age =
一旦用户从命令行提供输入,它应该看起来
[California]
$;FIrst_Name = Jack
$;Last_Name= Daner
$;Age = 27
[NewYork]
$;FIrst_Name = Jill
$;Last_Name= XYZ
$;Age = 30
[Washington]
$;FIrst_Name = Kim
$;Last_Name= ABC
$;Age = 25
每个位置的First_Name,Last_Name和Age的顺序可以更改,甚至位置的顺序也可以更改,但每个位置部分应保持独立且完整。到目前为止,我编写了以下代码,我的一些代码用于将整个文件放在一个哈希中,但我无法保留其中的每个位置部分!我尝试使用两个哈希 - 有人可以帮助我,因为它对我来说变得非常复杂!非常感谢。 (我有一个类似文件的另一个问题,但遗憾的是无法解决它)
已编辑的代码
Open the file
use strict;
use warnings;
use Getopt::Long;
sub read_config {
my $phCmdLineOption = shift;
my $phConfig = shift;
my $sInputfile = shift;
open($input.file, "<$InputFile") or die "Error! Cannot open $InputFile
+ for reading: $!";
while (<$input.file>) {
$_ =~ s/\s+$//;
next if ($_ =~ /^#/);
next if ($_ =~ /^$/);
if ($_ =~ m/^\[(\S+)\]$/) {
$sComponent = $1;
next;
}
elsif ($_ =~ m/^;;\s*(.*)/) {
$sDesc .= "$1.";
next;
}
elsif ($_ =~ m/\$;(\S+)\$;\s*=\s*(.*)/) {
$sParam = $1;
$sValue = $2;
if ((defined $sValue) && ($sValue !~ m/^\s*$/)) {
$phfield->{$sCategory}{$sParam} = ["$sValue", "$sDesc"];
}
else {
$field->{$sCategory}{$sParam} = [undef, "$sDesc"];
}
}
$sParam = $sValue = $sDesc = "";
next;
}
}
写新文件 -
sub write_config {
my $phCmdLineOption = shift;
my $phConfig = shift;
my $sOut = shift;
open(outfile, ">$sOut") or die " $!";
foreach $sCategory (sort {$a cmp $b} keys %{$fields}) {
print $outfile "[$sCategory]\n";
foreach $sParam (sort {$a cmp $b} keys %{$fields-{$sCategory}}) {
$sDesc = ((defined $phConfig->{$sCategory}{$sParam}[1]) $fields->{$sCategory}{$sParam}[1] : "");
print $outfile ";;$sDesc\n" if ((defined $sDesc) && ($sDesc !~ m/^$/));
$sValue = ((defined $fields->{$sCategory}{$sParam}[0]) ? $fields->{$sCategory}{$sParam}[0] : undef);
print $outfile "$sValue" if (defined $sValue);
print $outfile "\n";
}
print $outfile "\n";
}
close($outfile);
return;
注意 - 我也在PerlMonks论坛上发布了这个问题。非常感谢!
答案 0 :(得分:2)
我认为你在细节上迷失了,并且跳过了一些不必要地使问题复杂化的基础知识。那些基础是;
除此之外,你正朝着正确的方向前进,但是对于你遗漏的算法有一些见解,这进一步增加了复杂性。
首先,对于小数据解析数据,请注意匹配一种类型的行的可能性会立即取消其他类型行的匹配。所有elsif
都不是必需的,因为与类别匹配的行永远不会匹配LastName或Age,反之亦然。
其次,当您获得匹配时,看看您是否可以立即执行所需操作,而不是存储匹配结果以便稍后处理。在这种情况下,不是在变量中保存“组件”或“category”,而是将其立即放入您正在构建的哈希中。
第三,如果您要更新不大的文本文件,请考虑使用该文件的新版本,然后在程序结束时声明当前版本为旧版本,新版本为当前版本。这样可以减少无意中修改某些内容的可能性,并允许在执行后将更新与原始内容进行比较 - 如果需要,可以轻松地“回滚”更改您的哪一个用户可能会非常感谢一天。
第四,也是最重要的,你只需要担心几个属性或组件,所以在具体而不是抽象中处理它们。您可以在下面看到我已经遍历qw( First_Name Last_Name Age)
而不是哈希的所有键。很明显,如果你必须处理开放式或未知属性,你不能这样做,但在这种情况下,AFAICT,你的字段是固定的。
这是一个基本上适用于上述约束的版本。
#!/usr/bin/env perl
use v5.12 ;
use Getopt::Long ;
my %db ; # DB hash
my $dbf = "data.txt" ; # DB file name
my $dbu = "data.new" ; # updated DB file name
my $dbo = "data.old" ; # Old DB file name
my ($cat, $first, $last, $age) ; # Default is undef
GetOptions( 'cat=s' => \$cat ,
'first=s' => \$first ,
'last=s' => \$last ,
'age=i' => \$age
);
die "Category option (--cat=...) is compolsory\n" unless $cat ;
open my $dbh, '<', $dbf or die "$dbf: $!\n"; # DB Handle
open my $uph, '>', $dbu or die "$dbu: $!\n"; # UPdate Handle
# REs for blank line, category header and attribute specification
my $blank_re = qr/ ^ \s* $ /x ;
my $cat_re = qr/ ^ \[ (\w+) \] \s* $ /x ;
my $attr_re = qr/ ^ \$ ; (?<key>\w+) \s* = \s* (?<val>\N*) $ /x ;
while ( <$dbh> ) {
next unless /$cat_re/ ;
my %obj = ( cat => $1 ) ;
while ( <$dbh> ) {
$obj{ $+{key} } = $+{val} if /$attr_re/ ;
last if /$blank_re/
}
$db{ $obj{cat} } = \%obj
}
# Grab existing obj, otherwise presume we're adding a new one
my $obref = $db{ $cat } // { cat => $cat } ;
$obref->{ First_Name } = $first if defined $first ;
$obref->{ Last_Name } = $last if defined $last ;
$obref->{ Age } = $age if defined $age ;
# Update the DB with the modified/new one
$db{ $obref->{cat} } = $obref ;
for (sort keys %db) {
my $obref = $db{ $_ } ;
printf $uph "[%s]\n", $obref->{ cat } ;
for (qw( First_Name Last_Name Age )) {
printf $uph '$;' . "%s = %s\n", $_, $obref->{ $_ }
}
print $uph "\n"
}
close $dbh ;
close $dbu ;
rename $dbf , $dbo ;
rename $dbu , $dbf ;
exit 0
答案 1 :(得分:1)
此处的用户输入需要进行组织,为此我们可以为每个字段使用命名选项,并为状态使用一个。读取哈希值的Getopt option在这里很有用。我们还需要将这些选项的名称与字段名称相关联。有了这个,处理文件很简单,因为我们有一个现成的机制来识别感兴趣的行。
通过在ref-array上放置行,我们也可以保持顺序,并且该refarray是散列中section-key的值。散列不是必需的,但为未来的开发增加了灵活性。一旦我们完成它,我们也可以通过使用一个简单的辅助数组来保持部分的顺序。
use warnings;
use strict;
use Getopt::Long;
use feature qw(say);
# Translate between user input and field name ($;) in file
my ($o1, $o2, $o3) = qw(first last age);
my @tags = ('FIrst_Name', 'Last_Name', 'Age');
my %desc = ($tags[0] => $o1, $tags[1] => $o2, $tags[2] => $o3);
my (%input, $state);
GetOptions(\%input, "$o1=s", "$o2=s", "$o3=i", 'state=s' => \$state);
my $locinfo = 'loc_info.txt';
open my $in_fh, '<', $locinfo;
my (%conf, @sec_order, $section, $field);
while (my $line = <$in_fh>)
{
chomp($line);
next if $line =~ m/^\s*$/;
# New section ([]), for hash and order-array
if ($line =~ m/^\s*\[(.*)\]/) {
push @sec_order, $section = $1;
next;
}
# If we are in a wrong state just copy the line
if ($section ne $state) {
push @{$conf{$section}}, $line . "\n";
next;
}
if (($field) = $line =~ m/^\$;\s*(.*?)\s*=/ ) {
if (exists $input{$desc{$field}}) {
# Overwrite what is there or append
$line =~ s|^\s*(.*?=\s*)(.*)|$1 $input{$desc{$field}}|;
}
}
else { warn "Unexpected line: |$line| --" }
push @{$conf{$section}}, $line . "\n";
}
close $in_fh;
for (@sec_order) { say "[$_]"; say @{$conf{$_}}; }
调用
script.pl -state STATE -first FIRST_NAME -last LAST_NAME -age INT
可能会遗漏任何选项,在这种情况下不会触及该字段。如果命令行提供的字段有内容,则会覆盖该字段。 (这可以很容易地改变。)这适用于单状态条目,但如果需要,可以很容易地修改。
这是一个基本的解决方案。接下来的第一件事就是从文件本身读取字段名称,而不是让它们硬编码。 (这样就可以避免在FIrst
之前发现拼写错误=
和不一致的间距。)但是,添加的细化越多,进入模板开发的越多。在某个时刻,很快就会使用一个模块。
注意上面的正则表达式分隔符与其他地方(|
)不同,以避免编辑器将所有红色着色。