将字符串转换为perl

时间:2015-04-30 12:08:34

标签: arrays perl hash

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

  

" IP = 192.168.100.1,端口= 80,FILE = howdy.php",

我最终得到的哈希值如下:

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

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

my $paramList = $1;
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中吗?

2 个答案:

答案 0 :(得分:3)

我认为您所指的“演员”功能可能是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将数组转换为哈希

如果要将数组转换为哈希,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格式是一个不错的选择,因为它们是“数据为文本”的明确标准。 (如果您只是在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“打印”它们,那么这是大多数人都会识别的常见格式。所以你的'第一个哈希'看起来像:

声明为(不是 - 我的前缀,()用于声明,以及strict下所需的引号):

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

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

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

通过这种方式,您可以更快乐地重新构建和解释您的代码。

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

答案 1 :(得分:0)

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

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 = $1;
    my $value = $2 || $3 || $4;

    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}

<强>附录:

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

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

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

现在,你认为逗号不会改变;它还是分开的东西,不是吗?但它改变了它所分离的东西。这就是为什么第二行更难以解析。第二行在树中有三个上下文:

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

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

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

这是最后一条规则,为了完成。它不用于此问题。

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

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

<强>解释

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

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

Perl Best Practices中建议使用表达式末尾的选项/m/s/x,但只有/x选项才有影响。它允许在正则表达式中使用空格和注释。

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

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

再次对$qr_value_brace_1:结束括号只有一个含义。并且哈希也没有被解析。

$qr_value_3表达式将值RE合并为一个。 $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`一起使用,一次一个用于循环内部的处理。

这简要说明了代码中发生的事情。 ☺