读取两个哈希值中的文件以保留顺序

时间:2016-03-18 19:00:28

标签: perl hash

我正在尝试读取一个分类在某个位置下的用户信息的文件,我想使用用户输入填写一些字段并输出文件,同时保持每个位置下的字段完整,例如 - 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论坛上发布了这个问题。非常感谢!

2 个答案:

答案 0 :(得分:2)

我认为你在细节上迷失了,并且跳过了一些不必要地使问题复杂化的基础知识。那些基础是;

  1. 正确地缩进你的代码(这是令人惊讶的差异)
  2. 始终在正则表达式和大量空白处使用 / x 修饰符以提高可读性
  3. 使用大量正则表达式时,使用“引用规则”, qr ,从正则表达式使用中分离正则表达式定义
  4. 除此之外,你正朝着正确的方向前进,但是对于你遗漏的算法有一些见解,这进一步增加了复杂性。

    首先,对于小数据解析数据,请注意匹配一种类型的行的可能性会立即取消其他类型行的匹配。所有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之前发现拼写错误=和不一致的间距。)但是,添加的细化越多,进入模板开发的越多。在某个时刻,很快就会使用一个模块。

注意上面的正则表达式分隔符与其他地方(|)不同,以避免编辑器将所有红色着色。