Perl语言IC设计实践
上QQ阅读APP看书,第一时间看更新

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

000

第2组是“not”“and”“or”(见表1-2)。

表1-2 逻辑操作符2

000

不用背诵记忆它们之间的优先级,只要使用圆括号即可,例如:

(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。