【问题标题】:Parsing a file with repeating sections in Perl解析具有重复部分的文件
【发布时间】:2012-10-12 21:21:28
【问题描述】:

我正在尝试使用 Perl 来解析具有这样重复部分的文件:

系统:server1.domain.com 开始时间:20121021T01:00:56 停止时间:20121021T01:00:56 返回码:0 输出 ------ 用户1 用户2 用户3 ############################## 系统:server2.domain.com 开始时间:20121021T01:00:56 停止时间:20121021T01:00:56 返回码:0 输出 ------ 用户1 用户4 用户5 用户6

我能够将输入记录分隔符设置为 "##############################" 这会给我每个块作为单独的记录。

但我需要能够使用用户名作为每个服务器的键填充散列。

最好的方法是什么?

【问题讨论】:

    标签: perl parsing hash


    【解决方案1】:

    你应该看看Perl references

    在 Perl 5.0 之前的版本中,您拥有三种类型的数据结构,并且您只能在其中存储标量数据。例如,我可以有一个散列,但散列的每个值都可以是字符串或数字。

    Perl 5.0 引入了引用。引用是指向另一个数据结构的一段数据。例如,您可以有一个代表服务器的散列​​。散列的每个成员指向另一个包含用户(或用户列表,如果您愿意)的散列。

    例如,您有一个如下所示的哈希:

    $system{server1.domain.com}  --->  $anon_array[0] = "user1"
                                       $anon_array[1] = "user2"
                                       $anon_array[2] = "user3"
    
    $system{server2.domain.com}  ----> $another_anon_array[0] = "user1"
                                       $another_anon_array[1] = "user2"
                                       $another_anon_array[2] = "user3"
                                       $another_anon_array[3] = "user4"
    

    您可以在上面看到%system 哈希的键实际上指向内存中包含用户列表的某个数组。这些数组没有诸如@foo@bar 之类的名称。您可以访问它们的唯一方法是通过您的%system 哈希。因此,它们被称为匿名数组。

    要创建引用,请在变量前面加上反斜杠:

    $my_reference = \%my_hash
    

    现在,$my_reference 指向散列 %my_hash成员。如果我想再次将引用变为哈希,我在它前面加上哈希符号(%):

    %bar = %{$my_reference};
    

    您可以使用-> 语法来表明某物指向一个引用:

    $foo->[0];   Points to the first member of an anonymous array.
    
    $bar = [];    #Sets $bar to be a reference to an anonymous array
    $foo = {};    #Sets $foo to be a reference to an anonymous hash.
    

    现在,真正的乐趣可以开始了!您现在可以存储整个数据结构,而不是存储单个值。

    想象一下这样的事情:

    my %system;   #Normal hash keyed by domain name
    
    $system{server1} = {};  # This points to an anonymous hash!
    $system{server1}->{START}  = "20121021T01:00:56";
    $system{server1}->{STOP}   = "20121021T01:00:56";
    $system{server1}->{RETURN} = 0;
    $system{server1}->{USERS} = [];  #This hash entry points to an anonymous array
    $system{server1}->{USERS}->[0] = "user1";
    $system{server1}->{USERS}->[1] = "user2";
    $system{server1}->{USERS}->[2] = "user3";
    

    server2 以此类推。您有一个哈希%system,它以域名为关键字。 %system 散列中的每个域都有一个 START 时间、STOP 时间、RETURN 值、该系统上的 USERS 列表。 server1 的开始时间是几点?这是$system{server1}->{START}system2 的用户列表是什么?它是@{ $system{server2}->{USERS} }(对存储在$system{server2}->{USERS} 中的数组的取消引用)。

    需要一些时间来适应这种新的思维方式,但您可以看到它有助于将您的数据作为一个单一的结构保持在一起。

    当然,复杂的数据结构会带来保持直截了当的问题。例如:

    use strict;
    use warnings;
    
    my %server;
    $servre{domain1} = "10.10.1.20";
    

    会失败,因为我从未声明过$servre。然而:

    use strict;
    use warnings;
    my $hash = {};
    $hash->{SERVRE}->{domain1} = "10.10.1.20";
    

    会工作得很好。在这种情况下,SERVRE 是哈希引用的键,而不是变量。在这种情况下,use strict; 杂注不会检测到我的拼写错误。这将引导您进入下一步:面向对象的 Perl。但是,首先要了解这些新的复杂数据结构以及它们是如何工作的。在您对在程序中使用它们更加熟悉之后,您可以开始研究面向对象编程将如何帮助解决它们造成的混乱。

    【讨论】:

      【解决方案2】:

      尝试这样做:

      use strict; use warnings;
      use Data::Dumper;          # one of the top 5 modules you should know
      
      my $hash_of_hashes = {};   # a reference to a void HASH
      
      my $current;
      
      while (<>) {
          chomp;
          if (/^System:\s+(.+)/) {
              $current = $1;
          }
          elsif (/^([^:]+):(.+)/) {
              $hash_of_hashes->{$current}->{$1} = $2;
          }
      }
      
      print Dumper $hash_of_hashes; # Dumper is a function of Data::Dumper module
      # it prints all the data structure in a human readable way
      

      使用它:

      perl script.pl input_file.txt
      

      注意

      我假设System: 行一直与当前主机的第一行匹配。

      【讨论】:

      • 实际上,Dumper 以计算机可读的方式打印。碰巧我们人类也理解它。可以将 Data::Dumper 输出文件直接加载到 perl 中。
      【解决方案3】:

      一个有趣的问题,让我想使用paragraph mode。使用####... 作为输入记录分隔符当然是一个想法,但它有点不靠谱而且不够灵活。例如,$/ 必须是 literal,这意味着您必须拥有准确的字符数。

      如果您可以依赖输入中演示的双换行符,段落模式将分两部分读取每个“集合”,然后将 ####... 分隔符作为容易丢弃的第三部分,并且开始新数据集的信号。此外,通过这种方式,我们可以更轻松地访问“用户”部分,这可能有点随机,唯一决定性的特征是它前面有标题“输出\n------”。

      use strict;
      use warnings;
      use Data::Dumper;
      
      $/ = "";                             # use paragraph mode
      my @data = [];                       # first element must be array ref
      while (<DATA>) {
          unless (/^#+\s*$/) {             # if not delimiter
              push @{ $data[-1] }, $_;     # save data in the arrays last element
          } else {
              push @data, [];              # start new array (which becomes the last)
          }
      }
      my %hash;
      for (@data) {
          my ($sys, $out) = @$_;                  # $_ is an array ref w two elements
          my ($server) = $sys =~ /System:\s*(\S+)/;   # extract server name
          my @users = split /\n+/, $out;          # easy extraction of users
          splice @users, 0, 2;                    # remove header
          $hash{$server}{$_} = undef for @users;  # add key w undef value
      }
      
      print Dumper \%hash;
      __DATA__
      System:      server1.domain.com
      Start Time:  20121021T01:00:56
      Stop Time:   20121021T01:00:56
      Return Code: 0
      
      Output
      ------
      user1
      user2
      user3
      
      ##############################
      
      System:      server2.domain.com
      Start Time:  20121021T01:00:56
      Stop Time:   20121021T01:00:56
      Return Code: 0
      
      Output
      ------
      user1
      user4
      user5
      user6
      

      输出:

      $VAR1 = {
                'server1.domain.com' => {
                                          'user1' => undef,
                                          'user3' => undef,
                                          'user2' => undef
                                        },
                'server2.domain.com' => {
                                          'user5' => undef,
                                          'user1' => undef,
                                          'user4' => undef,
                                          'user6' => undef
                                        }
              };
      

      一些关于细节的注释:

      • $data[-1]$data[$#data] 一样,我只是觉得它看起来更具可读性。
      • 将一个空数组 ref 推送到@data 意味着我们开始收集一个新集合。这与上述说明结合使用。
      • 将数据保存在二维数组中可以省去将“不可预测”的用户名与其他数据区分开来的麻烦。
      • 在多个换行符上拆分输出块会去除我们数据中任何麻烦的尾随换行符,这很方便,因为chomp 仅在段落模式下去除双换行符(除非我们再次更改 $/)。
      • undef 作为值添加到用户名键中只是您可能希望在其中放置的任何其他值的占位符。
      • &lt;DATA&gt; 更改为&lt;&gt;,并替换Dumper 输出将允许您使用文件名args 或stdin 与脚本。这些功能仅用于演示。用法是:

      some_command | perl script.pl > output.txt
      perl script.pl input.txt > output.txt
      

      【讨论】:

        猜你喜欢
        • 2015-08-09
        • 2023-03-31
        • 2023-04-10
        • 1970-01-01
        • 2015-05-27
        • 1970-01-01
        • 2020-11-27
        • 1970-01-01
        • 2016-10-05
        相关资源
        最近更新 更多