将字符串转换为 perl 中的散列或数组

Cast a string into a hash or array in perl

我目前正在将逗号分隔的二元组字符串解析为标量散列。例如,给定输入:

"ip=192.168.100.1,port=80,file=howdy.php",

我最终得到的散列如下所示:

%hash =
{
    ip => 192.168.100.1,
    port => 80,
    file => howdy.php
 }

代码工作正常,看起来像这样:

my $paramList = ;
my @paramTuples = split(/,/, $paramList);
my %hash;
foreach my $paramTuple (@paramTuples) {
    my($key, $val) = split(/=/, $paramTuple, 2);
    $hash{$key} = $val;
}

我想扩展功能,从只获取标量到也获取数组和散列。因此,另一个示例输入可能是:

"ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 => val1, key2 => val2}",

我最终得到的散列如下所示:

%hash =
{
    ips => (192.168.100.1, 192.168.100.2), # <--- this is an array
    port => 80,
    file => howdy.php,
    hashthing => { key1 => val1, key2 => val2 } # <--- this is a hash
 }

我知道我可以逐字符解析输入字符串。对于每个元组,我将执行以下操作:如果第一个字符是 (,则解析一个数组。否则,如果第一个字符是 { 则解析哈希。否则解析标量。

我的一位同事表示,他认为您可以使用某种转换函数将看起来像 "(red,yellow,blue)" 的字符串转换为数组或将 "{c1 => red, c2 => yellow, c3 => blue}" 转换为散列。如果我走这条路,我可以使用不同的分隔符而不是逗号来分隔我的 2 元组,如 |.

这在 perl 中可行吗?

我认为您所指的 "cast" 函数可能是 eval

使用eval

use strict;
use warnings;
use Data::Dumper;

my $string = "{ a => 1, b => 2, c => 3}";
my $thing =  eval $string;
print "thing is a ", ref($thing),"\n";
print Dumper $thing;

将打印:

thing is a HASH
$VAR1 = {
            'a' => 1,
            'b' => 2,
            'c' => 3
          };

或者数组:

my $another_string = "[1, 2, 3 ]";
my  $another_thing = eval $another_string;
print "another_thing is ", ref ( $another_thing ), "\n";
print Dumper $another_thing;

another_thing is ARRAY
$VAR1 = [
            1,
            2,
            3
          ];

虽然请注意 eval 要求您使用适合适当数据类型的括号 - {} 用于匿名散列,[] 用于匿名数组。所以以你上面的例子为例:

my %hash4;
my $ip_string = "ips=[192.168.100.1,192.168.100.2]";
my ( $key, $value ) = split ( /=/, $ip_string );
$hash4{$key} = eval $value; 

my $hashthing_string = "{ key1 => 'val1', key2 => 'val2' }"; 
$hash4{'hashthing'} = eval $hashthing_string;
print Dumper \%hash4;

给出:

$VAR1 = {
      'hashthing' => {
                       'key2' => 'val2',
                       'key1' => 'val1'
                     },
      'ips' => [
                 192.168.100.1,
                 192.168.100.2
               ]
    };

使用map将数组做成hash

如果你想把一个数组变成一个散列,map 函数就可以了。

my @array = ( "red", "yellow", "blue" );
my %hash = map { $_ => 1 } @array; 
print Dumper \%hash;

使用 slices 个哈希值

如果您有已知值和已知键,您也可以使用 slice

my @keys = ( "c1", "c2", "c3" );
my %hash2;
@hash2{@keys} = @array;
print Dumper \%hash2;

JSON / XML

或者如果您可以控制导出机制,您可能会发现导出为 JSONXML 格式将是一个不错的选择,因为它们是 [=90= 的明确定义标准]. (如果您只是在 Perl 进程之间移动数据,您也许也可以使用 Perl 的 Storable)。

同样,采用上面的 %hash4(稍作修改,因为我不得不引用 IP):

use JSON; 
print encode_json(\%hash4);

给我们:

{"hashthing":{"key2":"val2","key1":"val1"},"ips":["192.168.100.1","192.168.100.2"]}

你也可以打印出来:

use JSON; 
print to_json(\%hash4, { pretty => 1} );

获得:

{
   "hashthing" : {
      "key2" : "val2",
      "key1" : "val1"
   },
   "ips" : [
      "192.168.100.1",
      "192.168.100.2"
   ]
}

这可以用一个简单的方式读回:

my $data_structure = decode_json ( $input_text ); 

风格点

就风格而言 - 我能否建议您格式化数据结构的方式并不理想。如果您使用 Dumper 'print' 它们,那么这是大多数人都能识别的常见格式。所以你的 'first hash' 看起来像:

声明为(不是-我的前缀,和()声明,以及strict下要求的引号):

my %hash3 = (
    "ip" => "192.168.100.1",
    "port" => 80,
    "file" => "howdy.php"
);

转储为({} 的括号,因为它是匿名散列,但仍引用字符串):

$VAR1 = {
          'file' => 'howdy.php',
          'ip' => '192.168.100.1',
          'port' => 80
        };

这样一来,当人们能够重构和解释您的代码时,您会更加开心。

另请注意 - dumper 样式格式也适合(在特定的有限情况下)通过 eval 重新阅读。

试试这个,但必须单独解析复合值。

my $qr_key_1 = qr{
  (         # begin capture
    [^=]+   # equal sign is separator. NB: spaces captured too.
  )         # end capture
}msx;

my $qr_value_simple_1 = qr{
  (         # begin capture
    [^,]+   # comma is separator. NB: spaces captured too.
  )         # end capture
}msx;

my $qr_value_parenthesis_1 = qr{
  \(        # starts with parenthesis
  (         # begin capture
    [^)]+   # end with parenthesis NB: spaces captured too.
  )         # end capture
  \)        # end with parenthesis
}msx;

my $qr_value_brace_1 = qr{
  \{        # starts with brace
  (         # begin capture
    [^\}]+  # end with brace NB: spaces captured too.
  )         # end capture
  \}        # end with brace
}msx;

my $qr_value_3 = qr{
  (?:       # group alternative
    $qr_value_parenthesis_1
  |         # or other value
    $qr_value_brace_1
  |         # or other value
    $qr_value_simple_1
  )         # end group
}msx;

my $qr_end = qr{
  (?:       # begin group
    \,      # ends in comma
  |         # or
    \z      # end of string
  )         # end group
}msx;

my $qr_all_4 = qr{
  $qr_key_1     # capture a key
  \=            # separates key from value(s)
  $qr_value_3   # capture a value
  $qr_end       # end of key-value pair
}msx;



while( my $line = <DATA> ){
  print "\n\n$line";  # for demonstration; remove in real script
  chomp $line;

  while( $line =~ m{ \G $qr_all_4 }cgmsx ){
    my $key = ;
    my $value =  ||  || ;

    print "$key = $value\n";  # for demonstration; remove in real script
  }
}

__DATA__
ip=192.168.100.1,port=80,file=howdy.php
ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 => val1, key2 => val2}

附录:

扩展parse如此困难的原因,一言以蔽之,上下文。第一行数据 ip=192.168.100.1,port=80,file=howdy.php 是上下文无关的。也就是说,其中的所有符号都不会改变它们的含义。 context-free数据格式可以单独用正则表达式解析。

规则#1: 如果表示数据结构的符号永远不变,则它是上下文无关格式,正则表达式可以解析它。

第二行,ips=(192.168.100.1,192.168.100.2),port=80,file=howdy.php,hashthing={key1 => val1, key2 => val2}是一个不同的问题。逗号和等号的含义发生变化。

现在,您认为逗号没有改变;它仍然把东西分开,不是吗?但它改变了它分开的东西。这就是第二行更难解析的原因。第二行三个context,在一棵树中:

main context
+--- list context
+--- hash context

tokienizer 必须在数据切换上下文时切换解析集。这需要一个状态机。

规则#2: 如果数据格式的上下文形成一棵树,那么它需要一个状态机和每个上下文的不同解析器。状态机确定正在使用哪个解析器。由于除根之外的每个上下文都只有一个父级,因此状态机可以在其当前上下文结束时切换回父级。

为了完成起见,这是最后一条规则。本题没有用到。

规则 #3: 如果上下文形成 DAG (directed acyclic graph) 或递归(又名循环)图,则状态机需要堆栈所以它会知道当它到达当前上下文的末尾时要切换回哪个上下文。

现在,您可能已经注意到上面的代码中没有状态机。它在那里,但它隐藏在正则表达式中。但隐藏它是有代价的:列表和散列上下文不被解析。只找到它们的字符串。它们必须单独解析。

解释:

以上代码使用qr// operator 来创建解析正则表达式。 qr// 运算符编译正则表达式和 returns 对它的引用。此引用可用于匹配、替换或其他 qr// 表达式。将每个 qr// 表达式视为一个子例程。就像普通的子例程一样,qr// 表达式可以用在其他 qr// 表达式中,从更简单的正则表达式构建复杂的正则表达式。

第一个表达式 $qr_key_1 捕获主上下文中的键名。由于等号将键与值分开,因此它会捕获所有非等号字符。变量名末尾的“_1”是我用来提醒自己存在一个捕获组的。

表达式末尾的选项/m/s/xPerl Best Practices中被推荐,但只有/x选项有效果。它允许在正则表达式中使用空格和注释。

下一个表达式 $qr_value_simple_1 捕获键的简单值。

下一个 $qr_value_parenthesis_1 处理列表上下文。这是可能的,因为右括号只有一个含义:列表上下文的结尾。但也是有代价的:列表没有被解析;只找到它的字符串。

又是$qr_value_brace_1:右大括号只有一个意思。而且散列也没有被解析。

$qr_value_3 表达式将值 REs 合二为一。 $qr_value_simple_1 必须在最后,但其他顺序可以任意。

$qr_end 解析主上下文中字段的结尾。末尾没有数字,因为它没有捕获任何东西。

最后,$qr_all_4 将它们放在一起以创建数据的 RE。

内部循环中使用的 RE,m{ \G $qr_all_4 }cgmsx,解析出主上下文中的每个字段。 \G 断言的意思是:如果自上次调用以来已经更改(或者从未调用过),则从字符串的开头开始匹配;否则,从最后一场比赛结束的地方开始。这与 /c/g``options to parse each field out from the$line` 一起使用,一次一个用于在循环内处理。

这就是代码中发生的事情。 ☺