- Perl语言IC设计实践
- 滕家海编著
- 2136字
- 2022-02-08 17:38:00
1.4 继续改进命令行参数
1.3节中,我们对参数的假设是过于严苛的,目的是降低代码的复杂程度。
不同的选项可能有不同数量的参数值,有的只有一个,有的可能有多个。之前也提到过,我们有意不支持开关类(只有选项,而没有对应的参数值)的选项。
本节,我们的目标是:
1)根据每个选项的不同要求,合理地存储每个选项的参数值。
2)重复的选项被视为错误。
3)如果用户输入了错误的参数,程序会输出相应的提示信息,并提前结束程序。
下面我们看看实际代码:
代码1-3 ch01/read_argument_v3.pl
1 #!/usr/local/bin/perl 2 3 my %rule_of_opt = ( 4 '-s' => { 5 'perl_type' => 'scalar', 6 }, 7 '-a' => { 8 'perl_type' => 'array', 9 } 10 ); 11 12 my ($opt, %value_of_opt) ; 13 for my $arg ( @ARGV ) { 14 if ( $arg =~ /^-/ ) { 15 $opt = $arg; 16 if ( exists $value_of_opt{$opt} ) { 17 print "Repeated option: $arg\n"; 18 exit 1; 19 } 20 else { 21 @{ $value_of_opt{$opt} } = (); 22 } 23 } 24 elsif ( defined $opt ) { 25 push @{ $value_of_opt{$opt} }, $arg; 26 } 27 else { 28 print "Un-support option: $arg\n"; 29 exit 1; 30 } 31 } 32 33 for my $opt ( keys %value_of_opt ) { 34 if ( exists $rule_of_opt{$opt} ) { 35 if ( ${$rule_of_opt{$opt}}{'perl_type'} eq 'scalar') { 36 if ( @{ $value_of_opt{$opt} } != 1 ) { 37 print "Error: only one parameter is expected to '$opt'\n"; 38 exit 1; 39 } 40 } 41 elsif ( ${$rule_of_opt{$opt}}{'perl_type'} eq 'array') { 42 if ( @{ $value_of_opt{$opt} } < 1 ) { 43 print "Error: one or more parameter is expected to '$opt'\n"; 44 exit 1; 45 } 46 } 47 else { 48 print "Error: unknown 'perl_type' of '$opt'\n"; 49 exit 1; 50 } 51 } 52 else { 53 print "Un-support option: '$opt'\n"; 54 exit 1; 55 } 56 } 57 58 for my $opt ( keys %value_of_opt ) { 59 print "$opt =>"; 60 for my $pv ( @{ $value_of_opt{$opt} } ) { 61 print " $pv"; 62 } 63 print "\n"; 64 } 65 66 exit 0;
这个程序分成4个部分:第1部分定义散列来描述选项的规则。第2部分读取命令行参数。第3部分根据规则检查命令行参数所在的散列。第4部分输出命令行参数。
第3~10行,我们声明并初始化了一个散列%rule_of_opt。它有两个键-s和-a,每个键对应的不是单纯的标量,而是一个嵌套的散列结构,由于这个散列结构没有明确的名称,我们也称它为匿名散列。这两个匿名散列各有一个键perl_type,且分别对应了一个字符串,分别是scalar和array。
第13~31行是一个for循环结构,它从@ARGV依次读取参数,把参数值赋值给标量$arg。
第14~23行的if分支,来判断$arg的类别。如果$arg是以短划线开头,紧跟数字、字母、下划线的任意组合,那么就认为$arg是一个选项,并赋值给另一个标量($opt)存储。
第16行中的exists判断某个键是否存在于散列中。如果$key是散列%one_hash的一个键,则exists $one_hash{$key}返回真,否则返回假。如果第16行的$opt此前已经存在于%value_of_opt中,则表明现在是第2次读取了,重复的选项是一种错误。
第21行,@{ $value_of_opt{$opt} }是一个数组,还记得我们之前介绍的Perl三种变量的起始字符吗?@开头的是数组,$value_of_opt{$opt}是散列%value_of_opt中的键$opt之所指。所以@{ $value_of_opt{$opt} }就是散列%value_of_opt中的键$opt指向的一个数组。我们把这个数组初始化成一个空的数组。
第24~26行,defined函数根据变量是否有值返回真假,如果有值,则返回真,否则返回假。如果$arg不符合第14行的if的条件要求,那么来到了elsif分支,此时需要判断,此前是否已经定义该选项了。如果已经定义,则把当前的$arg添加到该数组@{ $value_of_opt{$opt} }的尾部。
第27~30行,如果上述两个分支判断都失败,则来到了这个else分支,输出错信息,并终止程序,返回状态1。什么情况下,会执行到这个分支呢?是的,可能你也想到了,就是程序的第一个参数不是选项时。例如:
./read_argument_v3.pl non-option -a something
程序会输出:
Un-support option: non-option
第33~56行,遍历并检查已经存储在散列%value_of_opt中的选项和对应的参数值。在这个for循环中,包含了3层if/else判断。
第34行,判断选项($opt)是否存在于散列%rule_of_opt。如果不存在,就来到了第52~55行,输出信息,然后结束程序。如果存在,则继续下一层的判断。
第35~50行,判断散列%rule_of_opt中的键$opt,所对应的perl_type是scalar还是array。如果都不是,则来到了47~50行,输出信息,然后结束程序。eq操作符用于比对字符串,如果左右两边的字符串相同,则返回“真”,否则返回“假”。
第35~40行,处理${$rule_of_opt{$opt}}{'perl_type'}等于scalar的情形。处在if的条件中的@{ $value_of_opt{$opt} } != 1(见第36行)是Perl特有的灵活语法。“!=”是“不等于”的意思,它造就了一个“标量环境”,即假设左右两边都是标量。那么处在标量环境中的数组,意味着什么呢?它的意义很直观,就是该数组的元素数量。如果数组@{ $value_of_opt{$opt} }的元素的个数不等于1,则此表达式返回“真”,否则返回“假”。
第41~46行,处理${$rule_of_opt{$opt}}{'perl_type'}等于array的情形,检查此数组的元素数量,如果元素数量小于1,则被认为不符合程序规则,输出提示信息,然后终止程序。
第58~64行,输出所有选项和对应的参数值。
第66行,是程序的末尾,显式地结束程序,并设置返回值为0(零)。
下面将详细介绍本实例中出现的数据结构:数组的散列,散列的散列。
1.4.1 数组的散列
基本的散列如下:
%basic_hash = ( "keyA" => "valueA", "keyB" => "valueB", );
数组的散列则如下:
%array_of = ( "keyA" => ["a1", "a2", …], "keyB" => ["b1", "b2", …], );
散列的每一个键指向的不再是单一的标量,而是一个数组。这个数组(不带标志符@)的名称是$array_of{key},完整的(带标志符@的)名称是@$array_of{key},这样编写,Perl就知道这是一个散列的键指向的数组。为了使查看代码时更明确,我们可以加上一组不改变其意义的{},甚至添加空格,如@{ $array_of{key} },这样更清晰一些。那么就可以像普通数组一样取用该数组的元素,如${ $array_of{key} }[0]。
代码1-3中,为了初始化此数组使用以下代码(第21行):
@{ $value_of_opt{$opt} } = ();
就像初始化普通数组一样。如果想要在初始化散列时就设置这些数组的元素,那么我们可以这么写:
%array_of = ( "keyA" => ["A1", "A2",], );
此处的中括号表示一个匿名数组的引用,$array_of{"keyA"}实际上是一个指向右侧匿名数组的引用。有关引用的详细知识,会在后续章节进行介绍。
1.4.2 散列的散列
散列的散列:
%hash_of = ( "keyA" => { "k1" => "valueA1", "k2" => "valueA2", }, "keyB" => { "k1" => "valueB1", "k2" => "valueB2", }, );
$hash_of{"keyA"}指向一个散列,这种用法与数组的散列类似,此时$hash_of{"keyA"}是散列名(不带标志符%),完整的散列名是%{ $hash_of{"keyA"} },除了名称有点复杂以外,其余用法与普通散列一样。要取用此散列的值,使用${ $hash_of{"keyA"} }{"k1"} = "valueA1"。同理,如果你不会混淆,也可以写为$$hash_of{"keyA"}{"k1"}
散列和数组都可以依照以上规则进行任意深度的嵌套。