1.3 改进命令行参数
输入参数都在一个数组(@ARGV)中,这样使用起来还有些不便。我们还需要便捷地知道对应某个选项的参数值。
在开始编写代码之前,我们先约定:
1)所有的输入参数,由选项和对应的参数值组成。不存在某个不属于任何选项的参数值。
2)每个选项对应至少一个参数值。不支持没有参数值的选项,即类似开关的选项。
我们以Linux中常见的复制命令为例:
cp -i file_a file_b
其中“-i”就是一个没有任何参数值的选项,该命令只在file_b存在的情况下,询问用户是否继续复制动作覆盖file_b。file_a或file_b都不属于任何选项,cp命令只是依据它们的位置来决定其意义:第一个参数作为输入文件,第二个参数作为输出文件。
如果使用Perl程序完成上述cp命令的功能,依照上面的约定,程序(假设为mycp)的参数可设计成如下:
-input file -output file -overlap [yes|no]
相应地,我们的程序,需要这样运行:
mycp -input file_a -output file_b -overlap no
当然,这些选项的相对位置是随意的,也可以这样运行:
mycp -overlap no -output file_b -input file_a
本节,我们将改进代码1-1的读取命令行参数的程序,把参数都存储到一个散列(hash)中。我们假设每个选项对应一个参数值,不多也不少,并且用户的输入是正确的:-optA pv_a -optB pv_b …。
代码1-2 ch01/read_argument_v2.pl
1 #!/usr/local/bin/perl 2 3 my ($opt, %value_of_opt) ; 4 5 for my $arg ( @ARGV ) { 6 if ( $arg =~ /^-/ ) { 7 $opt = $arg; 8 } 9 else { 10 $value_of_opt{$opt} = $arg; 11 } 12 } 13 14 for my $opt ( keys %value_of_opt ) { 15 print "$opt => $value_of_opt{$opt}\n"; 16 } 17 18 exit 0;
如果我们运行:
./read_argument_v2.pl -a a1 -b b1 -c c1
那么程序会输出:
Command is: ./read_argument_v2.pl -a => a1 -b => b1 -c => c1
第3行,声明了两个变量:一个标量$opt,一个散列%value_of_opt。
第6~11行,是一个if/else判断结构。
第6行条件中的$arg =~ /^-/是一个正则表达式匹配,如果$arg以短横线(“-”)开头,那么该匹配返回1,否则返回空(就是什么都没有),通常称之为空字符串。有关正则表达式的内容,留在第3章进行详细介绍。
第7行,把$arg的值赋值给$opt,留给下一次循环(第10行)使用。
第10行,把$arg赋值给散列中对应的键$opt。也就是说,给散列%value_of_opt中增加一个键/值对,其中键是$opt,值是$arg。
下面我们依次介绍代码1-2中出现的散列、判断结构if/else以及“真”与“假”。
1.3.1 散列
散列就是无序的键/值对,假设只有标量和数组,没有散列,理论上,也可以表示各类数据。现有一组如下数据:
ZheJiang HangZhou JiangXi NanChang XiZang LaSa …
可以如下存储数据:
my @provinces = ("ZhaJiang", "JiangXi", "XiZang"); my @pccs = ("HangZhou", "NanChang", "LaSa");
假设我们想知道江西的省会,可以通过一个循环,找到JiangXi在数组@provinces中的序号(即1,数组的序号从0开始),然后把这个1存储在某个变量中(比如$n),然后取出$pccs[$n],这就是我们想要的信息。
由于这样的情形很常见,需要更高效简便的处理方式,因此很多高级语言都提供了散列或者功能类似的数据类型。Perl的散列名必须以%开头,后面紧跟一个字母或者下划线,后面还可以继续跟多个字母、数字或下划线。散列的初始化如下所示:
%pcc_of = ( 'ZheJiang' => 'HangZhou', 'JiangXi' => 'NanChang', 'XiZang' => 'LaSa', );
与数组类似,散列由圆括号包围,键/值对用逗号分隔。每个单元都指定了一个键,在=>的左边;同时指定了与这个键配对的值,在=>的右边。键和值都是标量。如果要取用某个键指向的值,可以这样:
print $pcc_of{'JiangXi'}, "\n";
输出:
NanChang
散列中的键是唯一的,即同一个散列中不存在两个同名的键。
散列的常用函数有keys、values等。keys返回散列的全部的键,组成一个数组。values返回散列的所有的值,组成一个数组。需要留意的是,keys返回的数组中,各键的次序与该散列初始化化时各键的次序无关。
散列是灵活的,可以增减键值对。可使用delete函数进行删除操作,也可直接赋值进行新增操作。
delete $one_hash{'akey'}; $one_hash{'akey'} = "something";
1.3.2 判断结构if
常用的判断结构有if/elsif/else。其中elsif/else分支是可选的,elsif分支可以有多个,else分支最多只能有一个。
if ( condition1 ) { sentences1 … } elsif ( condition2 ) { sentences2 … } elsif ( condition3 ) { sentences3 … } else { sentencesN … } outer_sentence …
上述判断结构会从if的条件开始判断,如果condition1为“真”,则执行后面大括号中的语句sentences1;如果为“假”,则继续检查下一个条件,直到某个分支的条件为“真”,就执行那个分支的语句,执行该语句后,离开整个if/elsif/else结构。如果没有任何条件为“真”,且存在else分支,则执行else分支的语句。
无论执行哪个分支的语句,都会离开整个判断结构,来到结构外部,继续后面的语句outer_sentence。
1.3.3 “真”与“假”
什么是“真”,什么是“假”?这既可以是一个深奥的哲学问题,也可以是一个简单的Perl语法问题。在这里,我们仅讨论后者。
Perl没有提供专门的变量或常量来表示“真”与“假”。任何标量(或常量)都可以成为判断结构的条件。那么Perl怎么判断这个标量是“真”还是“假”呢?它有以下规则:
- 未被赋值的,是假;
- 数字0,字符串'0'(零),空字符串'',是假;
- 其余皆为真。
my $t1 = 0; # false my $t2 = '0'; # false my $t3 = '' ; # false my $t4 = ' '; # true
除了单个的“真”“假”表达式,Perl还支持逻辑表达式的组合。有两组“或与非”逻辑操作符会经常使用。
第1组是“!”“&&”“||”(见表1-1)。
表1-1 逻辑操作符1
第2组是“not”“and”“or”(见表1-2)。
表1-2 逻辑操作符2
不用背诵记忆它们之间的优先级,只要使用圆括号即可,例如:
(expr1 or expr2) and (expr3 or expr4) and (! expr5) …
这样可使代码的逻辑结构更清晰,也避免了我们对优先级的预期出现误判。
既然有两组逻辑操作符,那么如何选择呢?有没有特殊的规则需要记忆?请放心,没有!我常用的是“and”“or”“!”,因为我在输入&&和||这类“叠词”时常常会少输入一个字符,造成语法错误。使用!而不是not的原因是,后者字符多了两倍,而且前者具有很好的警示作用(即取“反”)。
不需要记忆的优先级:
1)!、&&、||这一组的各个逻辑操作符的优先级分别高于相对应的not、and、or。
2)同一组内,not(!)的优先级高于and(&&)的优先级,and(&&)的优先级高于or(||)的优先级。
3)将优先级按从高到低排序并汇总起来就是:! 高于 not 高于 && 高于 and 高于 || 高于 or。