我目前正在将逗号分隔的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中吗?
答案 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
格式是一个不错的选择,因为它们是“数据为文本”的明确标准。 (如果您只是在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`一起使用,一次一个用于循环内部的处理。
这简要说明了代码中发生的事情。 ☺