往期相关文章:
Perl语言入门系列之一
Perl语言入门系列之二
Perl语言入门系列之三:文件输入与输出
Perl语言中的正则表达式及其使用
Perl正则表达式:字符与字符集
Perl正则表达式:正则匹配
Perl正则表达式:文本处理
Perl语言程序应用(见本文)
高级控制结构
为了更加方便、快捷的实现某些功能,Perl拥有很多其他的控制结构,这些结构也许不是必需的,但可以使程序更加简洁和具有逻辑性。
⑴判断结构
①unless控制结构
unless控制结构意为除非条件为真(也即如果条件为假)则执行某个模块命令,可以看成if控制结构的互补结构。Perl中的unless结构语句如下所示:
代码语言:javascript复制unless (condition) {
command;
}
#这个结构等效于if (!(condition)) {command},此外还有unless else结构,如下所示:
unless (condition) {command1} else {command2}
#其相当于:
if (condition) {command2} else {command1}
②elsif控制结构
无论是if else还是unless else都只能判断一个条件表达式的真假,如果需要结合多个条件表达式的真假来执行命令则需要多个控制结构进行嵌套。此外if elsif控制结构也可以进行多个条件的判断,如下所示为判断变量$n的数据类型:
代码语言:javascript复制chomp(my $n = <STDIN>);
if (! defined $n) {
print "The value is undef.n";
} elsif ($n =~ /^d $/) {
print "The value is an integer.n";
} elsif ($n =~ /^d*.d $/) {
print "The value is a floating number.n";
} elsif ($n =~ /^s*$/) {
print "The value is empty.n";
} else {
print "The value is an string.n";
}
理论上来说,中间elsif模块的数量是没有限定的。
⑵循环结构
①until控制结构
until控制结构意为直到某个条件为真(也即当条件为假),一直循环执行某个模块命令,可以看成while控制结构的互补结构。Perl中的until结构语句如下所示:
代码语言:javascript复制until (condition) {
command;
}
这个结构等效于while (!(condition)) {command}。
②for控制结构
Perl语言中的for循环和其他语言如C、R类似,相比while循环更加完善,包含关键字(for)、三个控制条件(初始化、条件、递归变化)和模块,语句结构如下所示:
代码语言:javascript复制for (初始化;条件;变化) {
command;
}
#一个具体示例如下所示:
for ($i = 1; $i <= 10; $i ) {
print "I can count to $i!n";
}
在Perl中for循环的控制条件非常灵活,可以为空(分号不可省),也可以为非数值文本操作,如下所示:
代码语言:javascript复制for ($_ = "bedrock"; s/^(.)//; ) {
print "One character is: $1n";
}
在for循环中,替换操作(注意实际上是_ =~ s/^(.)//)依次替换_中的首字母,直至全部替换完毕退出循环,运行如下所示:
在Perl中for循环和foreach循环是等价的,当控制条件只为一个列表时,for循环实际上就是foreach循环,也即关键字foreach可以直接写成for。
⑶表达式修饰符
为了使脚本更加简洁,Perl将控制结构简化为修饰符放在表达式语句后面,来控制该表达式的运行,常见的如if、unless、while、until、foreach修饰符,如下所示:
代码语言:javascript复制print "$n is a negative number.n" if $n < 0;
#该语句为下面if控制结构的简化:
if ($n < 0) {
print "$n is a negative number.n";
}
#此外还有类似的:
print "$n is not a negative number.n" unless $n < 0;
$n *= 2 until $n > 10;
print "It's a small number", ($n = 2) while $n < 10;
&greet($_) foreach @person;
⑷裸块控制结构
没有关键字或条件表达式的代码块被称为裸块(naked block)。例如如下循环结构:
代码语言:javascript复制while (condition) {
command;
}
#现在去掉关键字while和条件表达式condition,就会得到一个裸块:
{
command;
}
这看起来与单独的一个命令表达式并没有什么差别,然而一个裸块中的私有变量都是仅限于裸块内部有用,因此裸块控制结构起到划分代码层次的作用。
⑸自增自减
在控制结构尤其是循环结构中常常用到变量的自增与自减,需要用到自增操作符( ,变量加1)与自减操作符(--,变量减1),如下所示:
代码语言:javascript复制my $n = 3; #变量初始化
$n ; #$n变为4
$n--; #$n又变为3
变量自增/减也可以用来对变量进行赋值,分为前置自增/减和后置自增/减,如下所示:
代码语言:javascript复制my $m = 5;
my $n = $m; #前置自增,先自增后赋值,得$n为6,$m为6
my $n = $m ; #后置自增,先赋值再自增,得$n为6,$m为7
自减的规律与自增相同。自增可以放在foreach循环中用来判断已经出现过的条目,如下所示:
代码语言:javascript复制my @people = qw/ fred barney wilma dino barney betty pebbles /;
my %seen;
foreach (@people) {
print "I've seen you somewhere before, $_!n" if $seen{$_} ;
}
运行结果如下所示:
⑹结构控制
Perl是一种结构化的编程语言,因此需要多样化的结构控制方法,例如对多个循环结构选择执行,或是控制循环结构的执行次数。
①循环控制操作符
循环控制操作符位于循环结构内,用于搭配判断结构来控制循环的退出、选择、返回等,常见的循环控制操作符有以下三个:
last:在某个条件下立即终止循环的执行跳出循环,类似于C、R中的break;
next:在某个条件下立刻结束当前这次循环迭代,进入下一次循环迭代;
redo:重新执行当前循环该次迭代,直到满足某个条件,这常用来纠正用户输入错误。
next示例如下所示:
代码语言:javascript复制while (<>) {
foreach (split) {
$total ;
next if /W/;
$valid ;
$count{$_} ;
}
}
print "Total strings = $total, valid words = $validn";
foreach $word (sort keys %count) {
print "$word was seen $count{$word} times.n";
}
while循环中钻石操作符按行读取参数文件并将内容存入$_,foreach循环中将外层中$_内容按空格进行拆分成列表并将每一部分依次存入内层$_,内层循环中的标量变量和哈希其初始值均为undef,这里起到计数的作用。next操作符当匹配到非单词内容(非字母、数字、下划线)则回内层循环初始并进入下一个循环,运行如下所示:
②模块标签
Perl允许对模块添加自定义标签来实现更好的结构控制,模块标签一般为大写字母,放在模块最前面,中间冒号隔开,模块标签可以直接放在循环控制操作符后面用来进行结构控制,如下所示:
代码语言:javascript复制LINE: while (<>) {
WORD: foreach (splid) {
last LINE if /END/;
next WORD if /w/;
}
}
③条件操作符
条件操作符?:就像一个if-else结构,是一个三目操作符,使用格式如下所示:
expression ?if_true_comd : if_false_comd
最前面的表达式为判断依据,若为真则执行冒号前面的命令,若为假则执行冒号后面的命令。如下所示:
代码语言:javascript复制my $size =
($width < 10) ? "small" :
($width < 20) ? "medium" :
($width < 50) ? "large" :
"extre-large";
这就像一个三层嵌套的if-elsif-else结构。
④逻辑操作符
判断结构中常遇到多个多个表达式的组合判断,则需要用到逻辑操作符,有与操作符&&、或操作符||,如下所示:
代码语言:javascript复制if (expr1 && expr2) {} #两个表达式都为真则执行模块
if (expr1 || expr2) {} #两个表达式至少一个为真则执行模块
Perl模块
在Perl的不断发展过程中,世界各地的用户为了解决各种棘手问题,积累了大量已经成熟的开源Perl模块,多半可以在Perl综合典藏网(CPAN,https://metacpan.org/)上下载安装,这与R语言的软件包概念是类似的。
⑴模块下载安装
Perl模块有两种来源,一种是随Perl发行版本一同打包,安装Perl之后就可以调用,另一种需自己下载安装,在已知模块名字或者其中子函数名称的情况下,直接在CPAN进行检索。例如我们在CPAN检索PerlIO大类下的拓展模块gzip,结果如下所示:
进入PerlIO::gzip的主页,即可看到这个拓展模块的详细介绍——为Perl添加一个针对gzip压缩文件输入输出的数据层,方便处理gzip压缩文件。在主页右侧可以找到模块下载链接,下载到服务器后解压缩。解压后的文件夹里一般有文件ERADME或者INSTALL介绍模块安装方法,如下所示:
在第一条命令之后可以添加自定义安装路径,如下所示:
代码语言:javascript复制perl Makefile.PL INSTALL_BASE=/home/tengwenkai/perl5/lib
不过一般不建议这么做,除非默认安装路径(可以使用perl-V查看,在@INC数组中)没有写入权限,否则在模块使用时就得添加完整的路径或者修改默认搜索路径。在Perl中整合的命令cpan可以显示默认搜索路径下安装的所有模块,我们可以在其中查看已安装的gzip模块,如下所示:
上述模块实际上是使用MakeMaker封装,使用Perl自带的ExtUtils:: MakeMaker模块来编译并安装。当然,也有开发者使用其他方式例如辅助模块Module::Build,只需根据说明进行安装即可。使用cpan命令也可以方便的安装常用软件包。
⑵模块使用
在程序里使用模块,需要在程序开头用use指令声明加载该模块。在use引用模块时,如果模块名称中包含::双冒号,该双冒号将作为路径分隔符,相当于Linux下的/,当然这里前面省略了@INC数组中包含的默认搜索路径。使用自定义路径下的模块库,有以下两种方法:
代码语言:javascript复制use lib '模块路径';
BEGIN {
push @INC, '模块路径';
}
下面以常用的基础模块File::Basename为例,该模块中的basename函数用于返回一个包含路径的长文件名的基名(basename,也即去掉路径后的文件名),如下所示:
代码语言:javascript复制use File::Basename;
my $name = '/usr/local/bin/perl';
my $basename = basename $name; #返回'perl'给$basenam
当然,一个模块一般有很多个函数,例如上面加载的模块还有dirname函数。有时候我们只需使用某模块其中一个或几个函数,这时候只需加载对应的函数就行,以免多引入的函数名称和自定义子程序名称冲突,方法为加载模块时附上需加载的函数列表,如下所示:
代码语言:javascript复制use File::Basename qw/ basename /;
Perl应用实例
修改序列名称:
代码语言:javascript复制#!/perl -w
use warnings;
use strict;
open (FILE,"<$ARGV[0]") or die "can't open file $ARGV[0]";
open (OUT,">$ARGV[1]") or die "can't create file $ARGV[1]";
while(my $line = <FILE>){
$line =~ s/s $//ig; #删除末尾空白字符
$line =~ s/@//; #删除“@”符号
$line =~ s/:/_/ig; #将“:”替换为“_”
my @list = split/s /,$line; #使用空白符分割一行内容
print OUT "$list[0]t$list[2]n"; #只输出第一、三个字符
}
此脚本是将如下文件中序列名字进行修改,如下所示:
修改为:
程序只有一个输入文件和一个输出文件,通过替换操作来完成。