2

我正在尝试使用 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

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

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

实现这一目标的最佳方法是什么?

4

3 回答 3

1

尝试这样做:

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:line 始终与当前主机的第一行匹配。

于 2012-10-23T01:05:28.110 回答
1

您应该查看Perl 参考资料

在 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哈希键实际上指向内存中包含用户列表的某个数组。这些数组没有名称,例如@fooor @bar。您可以访问它们的唯一方法是通过您的%system哈希。因此,它们被称为匿名数组。

要创建引用,请在变量前面放置一个反斜杠:

$my_reference = \%my_hash

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

%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。但是,首先要了解这些新的复杂数据结构以及它们是如何工作的。在您在程序中使用它们变得更加舒适之后,您可以开始研究面向对象编程将如何帮助驯服它们造成的混乱。

于 2012-10-23T02:53:53.447 回答
0

一个有趣的问题,让我想使用段落模式。使用####...作为输入记录分隔符当然是一个想法,但它有点不稳定并且不那么灵活。例如,$/must be 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到用户名键只是您可能希望放在那里的任何其他值的占位符。
  • 更改<DATA><>并替换 Dumper 输出将允许您在脚本中使用文件名 args 或 stdin。这些功能仅用于演示。用法是:

some_command | perl script.pl > output.txt
perl script.pl input.txt > output.txt
于 2012-10-23T13:00:07.280 回答