【问题标题】:Populating an array of hashes with arrays of hashes用散列数组填充散列数组
【发布时间】:2010-12-04 06:54:57
【问题描述】:

我目前正在开发一款监控软件,它采用服务器名称和 IP 地址的输入文件,并创建一个基本的信息数据库。我想在处理配置文件时默认一些值,并且它在第一次循环时工作正常,但是任何后续条目都会以奇怪的方式创建(对我来说很奇怪是描述它的最佳方式,因为它可能是正确的并且代码是错误的,因为代码正在做我要求它做的事情,但不一定是我想要它做的事情)。

以下代码的输出如下所示:

$VAR1 = [
      {
        'IPAddress' => '196.8.150.163',
        'Boxname' => 'MPLRDFDSOAK1',
        'CurrentStatusInfo' => {
                                 'LineHandlersRunning' => [
                                                            {
                                                              'NumberOfGaps' => 0,
                                                        'LineHandlerName' => 'DEFAULT',
                                                        'NumberOfCommLinkDowns' => 0,
                                                              'LineHandlerUpTime' => 0,
                                                              'MemoryUsage' => 0
                                                            }
                                                          ]
                               },
        'PreviousStatusInfo' => {
                                  'LineHandlersRunning' => 
                        $VAR1->[0]{'CurrentStatusInfo'}{'LineHandlersRunning'}[0]
                                                         ]
                                }
      },
      {
        'IPAddress' => '196.8.150.164',
        'Boxname' => 'MPLRDFDSOAK2',
        'CurrentStatusInfo' => {
                                 'LineHandlersRunning' => 
                        $VAR1->[0]{'CurrentStatusInfo'}{'LineHandlersRunning'}
                               },
        'PreviousStatusInfo' => {
                                  'LineHandlersRunning' => 
                        $VAR1->[0]{'PreviousStatusInfo'}{'LineHandlersRunning'}
                                }
      }
    ];

以下是代码:

#######################################################################################
# Version History                                                                     #
#######################################################################################
# example of the ini file
#box=>MPLRDFDSOAK1;ip=>196.8.150.163
#box=>MPLRDFDSOAK2;ip=>196.8.150.164

use strict;
use warnings;

# include the library to allow easy access to command line arguments
use Getopt::Long;

# include the data dumper utility
use Data::Dumper;

my $usageInstructions = "Some instructions\n";
my $showMeTheInstructions = "";
my $iniFileToReadIn = "";
my @boxes;

# read in the command line arguments
GetOptions( "ini=s"  => \$iniFileToReadIn,
  "H|h|?!" => \$showMeTheInstructions);

if ($showMeTheInstructions)
{
 print $usageInstructions;
 exit 0;
}

readInINIFileIn($iniFileToReadIn, \@boxes) if ($iniFileToReadIn ne "");

print Dumper(\@boxes);
print "\n\#\n\# END OF DATA DUMP\n\#\n\n";
exit 0;

#######################################################################################
# subroutine to read in the ini file and create the empty records for the boxes 
# specified
sub readInINIFileIn
{ 
 my ($iniFile, $pointerToBoxes) = @_;

 my $noCRLFOnString = "";

 # open the file
 open (ConfigFile, "<$iniFile") || die $!;

 # read in all the lines into an array
 my @configurationItems = <ConfigFile>;

 # close the file
 close (ConfigFile);

 # temporary record storage
 my %tempRecord;

 # create the defaults for all boxes
 my @LineHandlersRunning;

 my %tmpLineHandlerRunning = ( LineHandlerName => "DEFAULT", 
     LineHandlerUpTime => 0, 
     NumberOfCommLinkDowns => 0, 
     NumberOfGaps => 0, 
     MemoryUsage => 0 );

 push (@LineHandlersRunning, {%tmpLineHandlerRunning});

 my %CurrentStatusInfo;
 my %PreviousStatusInfo;

 push @{ $CurrentStatusInfo{'LineHandlersRunning'} },          @LineHandlersRunning;
 push @{ $PreviousStatusInfo{'LineHandlersRunning'} },         @LineHandlersRunning;

 # loop through the config file and create the defaults for the database of boxes
 foreach my $configLine (@configurationItems)
 {
  my @TokenisedLineFromFileItems = ();
  my @TokenisedLineFromFileNameValuePairs = ();

  # store parameters
  # each line will be ; separated then => separated, as in each one will have a number of items separated by ;'s and
  # each item will be be a name & value pair separated by =>'s
  @TokenisedLineFromFileItems = split(/;/,$configLine);

  # remove quote marks around the outside of each element of the newly created array
  s/^"|"$//g foreach @TokenisedLineFromFileItems;

  # create information in database record to add to boxes
  foreach my $NameValuePair (@TokenisedLineFromFileItems)
  {
   @TokenisedLineFromFileNameValuePairs = split(/=>/,$NameValuePair);
   $noCRLFOnString = $TokenisedLineFromFileNameValuePairs[1];
   $noCRLFOnString  =~ s/(\n|\r)//g;

   $tempRecord{'Boxname'} = $noCRLFOnString if ($TokenisedLineFromFileNameValuePairs[0] eq "box");
   $tempRecord{'IPAddress'} = $noCRLFOnString if ($TokenisedLineFromFileNameValuePairs[0] eq "ip");
  }

  # add all other defaults as blank
  $tempRecord{'CurrentStatusInfo'} = {%CurrentStatusInfo};
  $tempRecord{'PreviousStatusInfo'} = {%PreviousStatusInfo};

  push(@$pointerToBoxes, {%tempRecord});
 }
}

【问题讨论】:

  • 您似乎忘记结束一句话:“任何后续条目都使用奇怪的 (...) 创建。”
  • 你知道,更长的变量名不一定是更好的变量名。 TokenisedLineFromFileNameValuePairs,我的意思是,真的吗?就目前而言,我的大脑无法理解这段代码应该做什么或它在做什么。您可以通过在撰写问题时付出一点努力来帮助其他人:确保Dumper 输出适合,确保输入数据样本合适,充分描述程序应该做什么以及它实际上是什么正在做。 怪异在旁观者眼中并不是一个足够好的描述。

标签: perl arrays loops populate hash


【解决方案1】:

我没有耐心浏览你所有的代码,但我敢打赌你的问题与Data::Dumper 输出的这方面有关:

$VAR1->[0]{'CurrentStatusInfo'}{'LineHandlersRunning'}[0]

换句话说,您的数据结构包含对结构其他部分的引用。

也许您认为您正在复制数据结构的一部分,但实际上您得到的是浅拷贝而不是深拷贝?例如,我怀疑这段代码:

$tempRecord{'CurrentStatusInfo'} = {%CurrentStatusInfo};
$tempRecord{'PreviousStatusInfo'} = {%PreviousStatusInfo};

如果问题确实与浅拷贝有关,Clone 模块可能会有所帮助。

【讨论】:

    【解决方案2】:

    使用词法文件句柄,在尽可能小的范围内声明变量。我不知道您的问题是什么,但很可能是由于某些变量的持续时间比您想象的要长。

    【讨论】:

      【解决方案3】:

      我猜这是因为这两行最终将相同的哈希引用推送到两个位置 - 因此,如果您在一个位置更改 hashref 内容,另一个也会更改,这可能不是您想要的默认值.

      正如 FM 指出的,这就是为什么在 Dumper 输出中有循环引用的原因。

      如果我正在等待下电话的人花费的时间足够长,我会为你重构代码。

      更新:好的,所以在不了解完整情况的情况下,很难说这是否是一种明智的方法。当然,您应该查看 CPAN 中的各种 INI 解析模块,但这里是您的代码的一个非常快速的调整,保留您现有的逻辑结构:

      use strict;
      use warnings;
      
      use Getopt::Long;
      use Data::Dumper;
      
      my $cmd_help = "Some instructions\n";
      my $show_help = "";
      my $ini_file_path = "";
      
      # read in the command line arguments
      GetOptions( "ini=s"  => \$ini_file_path,
                  "H|h|?!" => \$show_help );
      
      if ($show_help) {
          print $cmd_help;
          exit 0;
      }
      
      if (! -f $ini_file_path) {
          die "File '$ini_file_path' doesn't seem to exist.";
      }
      
      my $boxes = read_ini_file($ini_file_path);
      
      print Dumper($boxes);
      
      exit 0;
      
      =head2 read_ini_file
      
      read in the ini file and create the empty records for the boxes 
      
      =cut
      
      sub read_ini_file { 
          my ($ini_file) = @_;
      
          my @boxes;
      
          my @config_lines;
          {
              # consider using File::Slurp
              open (my $ini_fh, '<', $ini_file_path) || die $!;
      
              @config_lines = <$ini_fh>;
              chomp @config_lines; # remove \r\n
      
              # file handle will close when $ini_fh goes out of scope
          }
      
          # create the defaults for all boxes
          my %line_handlers_running_defaults = ( LineHandlerName => "DEFAULT", 
                                                 LineHandlerUpTime => 0, 
                                                 NumberOfCommLinkDowns => 0, 
                                                 NumberOfGaps => 0, 
                                                 MemoryUsage => 0 );
      
          # loop through the config file and create the defaults for the database of boxes
          foreach my $line (@config_lines) {
      
              my %record;
      
              my @token_pairs = map { s/^"//; s/^$//; $_ } split(/;/,$line);
      
              # create information in database record to add to boxes
              foreach my $pair (@token_pairs) {
                  my ($key, $val) = split(/=>/,$pair);
      
                  $record{Boxname} = $val if $key eq "box";
                  $record{IPAddress} = $val if $key eq "ip";
              }
      
              # add all other defaults as blank
              $record{CurrentStatusInfo} = { LineHandlersRunning => [{%line_handlers_running_defaults}] };
              $record{PreviousStatusInfo} = { LineHandlersRunning => [{%line_handlers_running_defaults}] };
      
              push @boxes, \%record;
          }
      
          return \@boxes;
      }
      

      给出这个输出:

      $VAR1 = [
            {
          'IPAddress' => '196.8.150.163',
          'CurrentStatusInfo' => {
                       'LineHandlersRunning' => [
                                      {
                                        'NumberOfGaps' => 0,
                                        'LineHandlerName' => 'DEFAULT',
                                        'NumberOfCommLinkDowns' => 0,
                                        'LineHandlerUpTime' => 0,
                                        'MemoryUsage' => 0
                                      }
                                    ]
                         },
          'Boxname' => 'MPLRDFDSOAK1',
          'PreviousStatusInfo' => {
                        'LineHandlersRunning' => [
                                       {
                                         'NumberOfGaps' => 0,
                                         'LineHandlerName' => 'DEFAULT',
                                         'NumberOfCommLinkDowns' => 0,
                                         'LineHandlerUpTime' => 0,
                                         'MemoryUsage' => 0
                                       }
                                     ]
                      }
            },
            {
          'IPAddress' => '196.8.150.164',
          'CurrentStatusInfo' => {
                       'LineHandlersRunning' => [
                                      {
                                        'NumberOfGaps' => 0,
                                        'LineHandlerName' => 'DEFAULT',
                                        'NumberOfCommLinkDowns' => 0,
                                        'LineHandlerUpTime' => 0,
                                        'MemoryUsage' => 0
                                      }
                                    ]
                         },
          'Boxname' => 'MPLRDFDSOAK2',
          'PreviousStatusInfo' => {
                        'LineHandlersRunning' => [
                                       {
                                         'NumberOfGaps' => 0,
                                         'LineHandlerName' => 'DEFAULT',
                                         'NumberOfCommLinkDowns' => 0,
                                         'LineHandlerUpTime' => 0,
                                         'MemoryUsage' => 0
                                       }
                                     ]
                      }
            }
          ];
      

      【讨论】:

      • 谢谢 标记您的示例,非常感谢您的帮助,因为它向我展示了我哪里出错了,并且实际上给出了如何纠正的指示,甚至更有帮助的是,简化了我的代码。再次感谢您,祝您有美好的一天。亲切的问候格伦
      猜你喜欢
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 2012-08-03
      • 1970-01-01
      • 2023-04-10
      • 1970-01-01
      • 2018-06-29
      • 2015-04-21
      相关资源
      最近更新 更多