Perl学习笔记
本文的PDF版本:http://wenku.baidu.com/view/bdbd8aeae009581b6bd9ebfe.html 学习笔记 简介Perl一般被认为是Practical Extraction andReport Language(实用获取与报表语言)的缩写,是由C以及sed、awk、Unix shell及其它语言演化而来的一种语言。它由语言学家Larry Wall最初发明及实现。Perl是一种为扫描任意的文本文件,从这些文本文件中获取信息,基于这些信息打印报表而优化的语言。它也很适合于完成许多系统管理的任务。Perl是一种粘合性语言,旨在实用(易用、高效、完整)而不是漂亮(优美、小巧)。其吉祥物是骆驼,取其虽并不漂亮却任劳任怨、能干活之特点。 Perl不随意限制数据的大小,只要你有充足的内存。递归的深度也不受限制。关联数组使用可以根据需要扩展以避免性能衰退。Perl能利用复杂的模式匹配技巧来快速扫描大量数据。尽管善于处理文本,Perl也能处理二进制数据。 Perl 5增加了模块化处理、面向对象编程、引进引用以处理多维数组等复杂的数据结构、Unicode支持、多线程支持等功能,使得Perl成为一种更加完备强大的语言。(以上编译自Perl manpage的Perl语言简介) Perl语言的座右铭是:There’s More Than One Way ToDo It (TMTOWTDI,有多种方法可以完成一件事,或者“条条道路通北京”)。Perl是一种自然和智能语言,它能根据上下文环境解释执行,同时有大量省略写法。 如果你通常想使用sed、awk或sh来解决的问题,但是却发现它们的能力不够,或者想运行得更快一点,却不想傻傻地用C来写,Perl将是很好的选择。Perl擅长于文本处理和系统管理,不适合于实时嵌入式系统编程、操作系统底层开发(比如驱动程序开发)、复杂的多线性共享内存应用以及极度大的应用。 Perl语言的长处: l????????强大的正则表达式和模式匹配功能(接近理想的正则表达式语言) l????????复杂灵活的数据结构(Array of Array,Array of Hash,Hash of Array,Hash of Hash等) l????????Unicode支持(相对AWK与C) 其不足是: l????????动态类型语言,不是很可靠 l????????自然语言,是优点也是缺陷,使得Perl语言代码可能晦涩难懂; l????????智能语言,是优点也产生不足:根据上下文解释编程者之意,可能产生臆断;也因此产生种种编程陷阱。 l????????不是很优美。多种方法做事,有时会让编程者无所适从。 l????????性能问题。Perl灵活的数据结构和处理性能通常不是很高(相对C/C++),若需要高性能的处理有时需要使用其它语言重写。 Perl语言的学习曲线浅而长。只要看看《Learning Perl》,就可以编写简单的Perl程序了,然而要深入掌握Perl,要使用Perl的复杂数据结构,进行面向对象编程、多线程编程则不是那么容易的事,需要很长的时间学习和实践。 概览Perl有以下几种数据类型:标量(Scalars)、数组(Arrays)、关联数组(Associative Arrays,或称Hash)、子程序(Subroutine)和Typeglob(*标识,指所有以上几种类型)。这些类型都有不同的符号标识。这些类型的说明如下:
(以上引自《Programming Perl》) 关于Perl的类型可以做如下说明: l????????不同的数据类型不同的命名空间,所以三种的标识符可以相同但互不干扰,即$array,@array,%array是完全不同的三个变量。 l????????Perl的数据类型是大小写敏感的;直接使用,不用声明(这一点可能成为缺陷,可以使用use strict语句强制必须声明) l????????引用(references)是Perl 5引进的一种特殊的标量。 l????????变量的命名以下划线或字母开头,可以是任意长度(1-251)。 l????????文件句柄(Filehandle)指给一个文件、设备、Socket或管道的名称。 l????????子程序相对其它类型是动词(其余为名词),较为特别,将用特别的一章详细说明 命名空间(Namespaces)Perl有两种命名空间,分别是符号表(symbol tables,也称为包(package))和词汇范围(lexical scopes,可以理解为局部空间)。符号表是存储全局变量的全局关联数组(包括存储其它关联数组),词汇范围是无名的空间,不存在于任何符号表中,而是与你程序中的一段代码块相关联。它们包含只对该代码块可见的变量。以our定义的变量存在符号表中,又称为全局变量或包变量(它们对包来说名义上是私有的,但是由于包本身是全局的,所以又是全局的),以my定义的变量存储在词汇范围中,也称为局部变量。 在任一命名空间中,任一变量类型都有其子命名空间(由其前面的字符决定),用户可以给标量、数组、关联数组、文件句柄、子程序名、标号起相同的名字。Perl的保留字也不会与变量名冲突。 Perl做名字查找的次序如下: l????????查找最小一级的包含的程序块,看变量是否在同一块中声明(my或者our) l????????查找更高一级的包含程序块进行查找 l????????查找整个编译单元看是否有声明 l????????如果没有找到,Perl将假定变量是包变量 l????????如果没有包定义,Perl将在无名的最高层包(main)中查找,$::bert等同于$main::bert 除了用my与our声明变量外,Perl中还有一种local的声明方式,注意local不是声明一个局部变量,而是使全局变量局部话,如果声明时未赋值,则所有的标量被初始化为undef,所有的数组与关联数组被初始化为()。其使用方式比如: ???? if ($sw eq ‘-v’) { ????????? local @ARGV = @ARGV; ????????? unshift @ARGV,‘echo’; ????????? system @ARGV; ???? } 又如当需要输出CSV格式的文件时可以使用local $,= “,”; local $/=”/n”;免得每次print都得写许多”,”和最后的”/n”; 标量(Scalars)标量是一个字符串、数值或者指向某类型的引用。 Perl的数值常量可以如下使用: $x = 12345;????????????? #整数 $x =12345.67;??????????? #浮点数 $x = 6.02e23;???????????? #科学计数法 $x = 4_294_967_296;????? #因为,是分隔符,所以4,294,967,296不能成立,Perl用_代替 $x = 0377;?????????????? #八进制 $x = 0xffff;?????????????? #十六进制 $x =0b1100_0000;???????? #二进制 ? Perl的字符串常量通常由单引号或双引号括起来。双引号支持变量和转义字符序列替换;而单引号只支持两个转义字符 /’和/”。双引号支持的转义字符如下:
Perl除了使用单引号和双引号来引,还可以使用q//与qq//的更一般格式,Perl的引用结构如下:
(引自《Programming Perl》) Perl的标量相关函数有:
? Perl的标量使用注意事项如下: l????????标量字符串可以是任何大小,以内存为限。 l????????Perl由字符串向数值的自动转换不能识别0,0b,0x,必须使用oct函数来做这种转换工作。 l????????C/C++中的char、int、long、double、string在Perl中全部抽象为标量。它可以是字符串、数或引用。. l????????Perl根据上下文来对待Scalars,认定其是字符串还是数值,即可以自动进行字符串与数值的互换(这一点从AWK继承而来)。 l????????在Boolean上下文中,一个标量值是false如果这个标量是空字符串””,数值0或者字符串”0”或者未定义(undef)。 l????????Perl将整数放在放在计算机的浮点寄存器中,所以整数被当作浮点数看待。 数组(Arrays)Perl的数组是在C语言基础上的扩展而来,它的使用更加自由、灵活。Perl数组的使用说明与注意事项如下: l????????它可以包含任意多个元素:最小的数组可以不含元素,而最大的数组可以占满全部可用内存; l????????数组下标默认从0开始,可以通过$[改变下标,最后一个下标是$#array_name,所以下列等式总是成立的 scalar(@array_name) ==$#array_name - $[ +1; l????????若访问不存在的数组元素,结果为null (而不会产生错误); l????????若给超过数组大小的元素赋值,则数组自动增长,原来没有的元素值为null; l????????数组的元素不必是相同类型的,它可以是数值、字符串或引用的任意组合; l????????数组下标可以为负值,表示从数组末尾算起的数组元素(-1相当于$#arary_nam),但是应知道,使用下标的绝对值不能超过数组大小(若数组@array有10个元素,不能给$array[-11]赋值,若只是访问,则为未定义)。 l????????数组的初始化方式: @array = (0,23,“point”,3);? @array = 1..100; @array = qw/a b c d/; 对于数组的常见操作是排序,Perl数组的排序用sort函数来实现,但是应记住,即使对于数值数组,sort也是按照字符顺序排序的,要实现按数值排序,应使用:sort { $a<=> $b} @array; 反向排序,使用reverse sort @array; 反向数值排序,使用sort {$b <=> $a} @array; 不分大小写排序,使用sort {lc($a) <=> lc($b)} @array;这里$a,$b的使用很特别,它们都是默认的包全局变量,所以在程序中一般不要使用变量$a和$b;这种排序方式是Perl实用而难看的体现之一。 数组相关函数如下:
? 关联数组(Hashes)虽然数组很有用,但它们有一个显著的缺陷,即很难记住哪个元素存储什么内容。这个问题产生的原因是数组元素要通过数字下标访问。关联数组是Perl语言最具特色的地方,也是Perl能够成为CGI程序首选的重要条件。关联数组是以任意字符串作为下标的数组。 关联数组的定义方式为%hash = (key1,value1,key2,value2,…),Perl5允许使用”=>”来分隔下标与值。用”=>”可读性更好。比如: %salary = (“Tom”=>1000,“Jack”=>1298,“Roses”=>1892)关联数组总是随即存储的,它们不会以创建的顺序出现。 创建一个关联数组元素的最好方法是赋值,如$salary{“Jane”} =223,删除用delete。如 delete($salary{“Tom”})。一定要使用delete函数来删除关联数组的元素,这几乎是唯一的方法。一定不对关联数组使用内部函数push,pop,shift,unshift及splice,因为其元素位置是随机的。 %hash在标量环境中使用,将返回使用的桶与分配的桶的比值,要找到关联数组中键的数量,使用scalar(keys %hash);注意$hash{$x,$y,$z}与@hash{$x,$z}的区别:$hash{$x,$z}是一个值,相当于$hash{ join $; => $a,$c }; @hash{$x,$z}是关联数组的切片,是三个值。 列出关联数组的键与值,keys (%hash)操作符可以生成由关联数组%hash中由关键字组成的列表,但是返回的元素不以任何固定顺序出现!可以用sort()函数对keys()返回值进行排序.
?? values %hash将生成值的列表,得到的顺序与keys %hash相同。Perl5允许对值直接进行修改: ?? for (@hash{keys %hash}) {s/foo/bar/g}???#老方式 ?? for (values %hash) { s/foo/bar/g}????????#新方式 ?? each %hash将遍历关联数组的key/value对,一次一对。在使用DBM时,最好使用each而不是keys和values,否则将一次将所有数据读入内存。each得到的(key,value)顺序与keys和values相同。另外,应注意不要在遍历时删除关联数组的值,但是例外是在删除ecch()刚刚返回的项总是安全的,就是说,以下代码可以正常工作: while (($key,$value) = each %hash) { ??? print $key,“/n”; ??? delete $hash{$key};??#This is safe! } 测试%hash中某key是否存在,应注意分别defined $hash{$key}与exists $hash{$key}的分别。 引用(References)2.6.1 Perl引用简介Perl5的重要新特性之一是能够处理多维数组与嵌套关联数组等复杂的数据结构。这一特性是由于引进引用后获得的,使用引用是Perl处理复杂数据结构的秘诀。这里说的引用现在叫硬引用(Hard Reference),相对于Perl4就存在的符号引用(SymbolicReference,也叫SoftReference)。硬引用是实在的引用。在Perl4中(与其借鉴的AWK类似),数组与关联数组的值必须是标量,为了在不改变最初设计的基础仍能使用复杂的数据结构,其解决方案是使用引用。引用是指向一个数组或关联数组或其它东西的标量。 Perl引用的使用是简单的。只有一个原则:Perl不会自动引用或反引。当标量是一个引用时,它总是像一个普通标量一样。它不会自动神奇地变成一个数组、关联数组或者子函数;用户必须显式地告诉Perl这样做,通过反引。 2.6.2创建引用创建引用的一种方式是使用反斜杠(/),这一操作符类似于C的取址操作符(&)。比如: $scalarref =/$foo; $constref =/186_282.42; $arrayref =/@ARGV; $hashref = /%ENV; $coderef =/&handler; $globref =/*STDOUT; 创建引用的另一方法是创建无名数组与关联数组:创建无名数组使用[ ITEMS ]方式,创建无名关联数组使用{ ITEMS }。无名子程序使用不带子程序名称的sub语句。例如: ?$aref =?[ 1,“foo”,13 ]; ?$href =?{ APR => 4,AUG => 8 }; ?$aref = [ 1,2,3 ]; #相当于@array = (1,3);?$aref = /@array; $coderef = sub { print “Boink!/n” }; 还有一种创建引用的方式是使用 *foo(THING)的方式: ?$scalarref?=?*foo{SCALAR}; $arrayref??=? *ARGV{SCALAR}; $hashref??=?? *ENV{HASH}; $coderef??=? *handler{CODE}; $ioref????=? *STDIN{IO}; $globref??=? *foo{GLOB} $formatref?= *foo{FORMAT} 说明: l????????引用一旦创建,就可以如其它标量一样使用; l????????无名数组与关联数组的创建方式可以组合使用,生成Aarray of Arrays,Array of Hashes,Hash of Arrays, Hash of Hashes等复杂的数据结构,下一章专门用来讨论这些数据结构。 l????????适当类型的引用在反引假定它存在时可以自动生成(Autovivification) l????????@list = (/$a,/@b,/%c)等价于 @list = /($a,@b,%c) l????????/(@foo)不同于/@foo,前者是对@foo内容的引用,而不是@foo本身 l????????子程序可以返回引用,但是需要小心,因为花括号{ }已经有其它使用方式。 sub hashem {???? { @_ }?} #错误!实际返回@_,要返回引用,请使用下面的方式: sub hashem {???? +{ @_ } } #OK sub hashem { return { @_ } } #OK l????????对象的构造函数也通常返回引用: $objref = Doggie::->new(Tail => ‘short’,Ears => ‘long’); $objref = new Doggie:: Tail => ‘short’,Ears => ‘long’; 2.6.3使用引用规则一:使用数组和关联数组引用时,总是可以在原来使用数组或关联数组名的地方,使用花括号{}内的数组引用或关联数组引用。例如:
规则一可以处理任何需求,但是实际用起来可能比较繁琐。对于常见的获取某一个元素的操作,可以如下更方便的方式: 规则二:${$aref}[3]可以写为$aref->[3]; ${$href}{red}可写为$href->{red}。 规则三(箭头规则):在两个下标之间,箭头是可选的。 所以, $a[1]->[2]可以写为$a[1][2];有了这一个规则,三维数组元素可以用易读的方式$x[2][3][5],而不是${${$x[2]}[3]}[5]。 使用引用的注意事项如下: l????????$aref->[3]与$aref[3]是完全不同的:$aref[3]是与@{$aref}完全不同的数组@aref的第四个元素。 l????????$href->{red}与$href{red}也完全不同:$href{red}是关联数组%href的某一元素。 l????????在使用规则一时,若花括号内是原子引用,可以省略花括号,如@$aref等价于@{$aref}; $$aref[1]等价于${$aref}[1](可是使用$aref->[1]也是一种选择) l????????将引用赋值给另一引用并不拷贝底层数据结构:$aref2 =$aref1;将得到一个数组的两个引用。要拷贝数组,使用 $aref2 = [ @{$aref1 } ];要拷贝关联数组,使用 $href2= { %{ $href1 } }; l????????要看一个变量是否是一个引用,使用”ref”函数。如果它的参数是引用,将返回真值。它返回”HASH”如果是关联数组引用,返回”ARRAY”如果是数组引用。 l????????如果试图像字符串一样使用引用,将得到 ARRAY(0x80f5dec)或者HASH(0x826afc0)之类的字符串,如果看到这样的字符串,一般说明错误地输出了引用。不过这一特征可以让我们使用eq(使用==会更迅速)来看两个引用是否指向同一样东西。 2.6.4符号引用如果引用已经定义,而不是硬引用,将被看做是符号引用。符号引用的使用方式如下: $name = "foo"; ??? $$name = 1;???????????????? # Sets $foo ??? ${$name} = 2;?????????????? # Sets $foo ??? ${$name x 2} = 3;?????????? # Sets $foofoo ??? $name->[0] = 4;???????????? # Sets $foo[0] ??? @$name = ();??????????????? # Clears @foo ??? &$name();?????????????????? # Calls &foo() (as in Perl 4) ??? $pack = "THAT"; ??? ${"${pack}::$name"} = 5;??? # Sets $THAT::foo without eval 如果只想使用硬引用,使用use strict ‘refs’;如果要允许使用符号引用,使用no strict ‘refs’;语句。 符号引用的使用注意事项: l????????只有全局变量对符号引用是可见的,所以以下代码将输出10而不是20 local $value = 10; ??? $ref = "value"; ??? { ??????? my $value = 20; ??????? print $$ref; }
l????????? 注意以下两行代码的区别: ${identifier}???? #等同于$identifier ${“identifier”}? #符号引用,在use strict ‘refs’下将报错。 l????????Perl继承了Shell对变量的使用方式,所以, $push = "pop on"; print ${push}over; ???? 将输出pop on over ???? 可以用$hash{ aaa }{ bbb }{ ccc }来代替${"aaa" }{ "bbb" }{ "ccc" }。 ???? 若要使用$hash{ shift }可以用$hash{shift() }或者$hash{ +shift }或者$hash{shift @_ };
2.6.5垃圾回收与弱引用Perl使用一个基于引用的垃圾回收器。如果使用循环引用,那么普通的垃圾回收机制将难以回收对象。比如{ my ($a,$b); $a=/$b; $b=/$a; }。解决这一问题的方法是使用弱引用(Weak references),这需要CPAN的WeakRef包配合。不过弱引用只是实验特性。 Perl5之前的Perl只能处理处理简单的数据结构(标量,一维Array、一维Hash),它可以像AWK模拟多维结构。Perl5引进了引用的概念,Perl所能处理的数据结构一下子复杂了起来。但是应记住Perl的Array和Hash本质上都是一维的,它们只能装标量值(字符串、数值或引用),它们不能直接装其它Array或Hash。Array of Arrays实际上是Array of references to arrays,Array of Hashes实际上是Array of references to hashes,余此类推。最常见的复杂数据结构有:Array of Arrays、Hash of Arrays、Array of Hashes、Hash of Hashes、Hash of Subroutines。尽管还可以构建更复杂的结构,但是一般不建议使用(过于复杂)。 Array of Arrays又称为二维数组或矩阵。定义二维数组的方法: @AoA = ( ????????? [“fred”,? “barney” ], ????????? [“george”,“jane”,“elroy”], ????????? [“homer”,“marge”,“bart” ], ??????? ); ?print $AoA[2][1]; ? ?$ref_to_AoA = [ ??????????????? [“fred”,?“barney” ], ??????????????? [“george”, ????????????? ??[“homer”, ????????????? ]; ? print $ref_to_AoA->[2][1]; ? 产生Array ofArrays的方法: # 从文件读取 while ( <> ){ push @AoA,[ split ]; } ? # 使用引用 while ( <> ){ push @$ref_to_AoA,[ split ]; } ? # 调用函数 for $i (1 .. 10) { ?? $AoA[$i] = [ somefunc($i) ]; } ? # 加入已存在的行 push @{ $AoA[0] },“Wilma”,“betty”; ? 使用多维数组的方法: $AoA[0][0] = “Fred”;?????????? #使用一个元素 $AoA[1][1] =~s/(/w)//u$1/;????? #另一个元素 ? # 使用引用打印所有元素 for $aref (@AoA ){ print “/t [ @$aref ],/n”; } ? # 使用指标打印 for $i ?( 0 .. $#AoA ) { print “/t [ @{$AoA[$i] } ],/n”; } ? # 使用双指标打印 for $i ?( 0 .. $#AoA ) { ?? for$j ( 0 .. $#{ $AoA[$i] } ) { ??????print “elt $i $j is $AoA[$i][$j]/n”; ?? } } ? 使用注意事项: l????????不要企图用 print “@AoA”的方式来打印整个多维数组。 l????????以下创建多维数组的方式是错误的: for $i ( 0 .. 10) { @array =somefunc($i); $AoA[$i] =@array;????? # 错误!等价于$AoA[$i] = scalar @array; ?????????????????????? # 使用 $AoA[$i] = /@array; 同样错误 } l????????正确的方式有如下几种: $AoA[$i] = [ @array];???????#推荐方式 ? for $i ( 0 .. 10) {??????????????#与上面错误方式只有微妙差别,却是正确的! my @array =somefunc($i); $AoA[$i] =@array; ??? } ??? @{ $AoA[$i] } = @array;??????#正确,却有点难懂 定义与生成Hash of Arrays的方式与使用多维数组类似,其主要差别在于使用上,因为使用Hash of Arrays存在输出排序的问题。 定义Hash ofArrays的方式: %HoA = ( ???? flintstones => [ “fred”,“barney” ], ???? jetsons => [“george”, ???? Simpsons => [“homer”,“bart”], ); ? 增加另一数组到Hash中: $HoA{teletubbies}= [“tinky winky”,“dipsy”,“laa-laa”,“poo” ]; ? 增加一个新元素到已存在的数组中: 基本思想:? $HoA{$s} = [ @array ]; ? # 从文件读入 # flintstones:fred? barney?wilma?dino push @{$HoA{flintstones} },“wilma”,“pebbles”; 产生Hash of Arrays方法: while ( <> ){ next unless s/^(.*?):/s*//; $HoA{$1} = [ split ]; } ? # 读入文件格式同上 while ( $line =<> ) { ?? ($who,$rest) = split /:/s*/,$line,2; ?? @fields = split ‘ ‘,$rest; ?? $HoA{$who} = [ @fields ]; } ? # 调用函数 for $group? ( “Simpsons”,“jetsons”,“flintstones” ) { $HoA{$group} = [ get_family($group) ]; } ? 使用Hash ofArrays: # 使用一个元素 $HoA{flintstones}[0] = "Fred"; # 另一个元素 $HoA{simpsons}[1] =~ s/(/w)//u$1/; # 打印所有 foreach $family ( keys %HoA ) { ???? print "$family: @{ $HoA{$family} }/n" } # 带指标打印所有 foreach $family ( keys %HoA ) { ???? print "family: "; ???? foreach $i ( 0 .. $#{ $HoA{$family} } ) { ???????? print " $i = $HoA{$family}[$i]"; ???? } ???? print "/n"; } # 按数组元素的个数排序进行打印 foreach $family ( sort { @{$HoA{$b}} <=> @{$HoA{$a}} } keys %HoA ) { ???? print "$family: @{ $HoA{$family} }/n" } # 按打印数组元素的个数及名字排序进行打印 foreach $family ( sort { ??????????????????????????? @{$HoA{$b}} <=> @{$HoA{$a}} ??????????????????????????????????????? || ??????????????????????????????????? $a cmp $b ??????????? } keys %HoA ) { ???? print "$family: ", join(",", sort @{ $HoA{$family} }), "/n"; } Arrays of Hashes相比其它几种数据结构不是特别常用。它在需要访问一系列记录而每一条记录包含key/value对时适用。 Arrays of Hashes的形成: @AoH = ( ????????? { Husband => “barney”, Wife??=> “betty”, Son???=> “bam bam”, }, { Husband => “george”, Wife??=> “jane”, Son???=> “elroy”, ?????????? }, ??????? ); 增加另一个元素: push @AoH,{Husband => “fred”,wife => “wilma”,?Son => “pebbles” }; ? # 从文件读取 # format:Lead=fred FRIEND=barney while ( <> ){ $rec = {}; for $field ( split ) { ??? ($key,$value) = split/=/,$field; ??? $rec->{$key} = $value; } push @AoH,$rec; } ? # no temp while ( <> ){ push @AoH,{ split /[/s=]+ ?}; ? # 增加key/value到一个元素 $AoH[0]{pet} = “dino”; ? 访问Arrays of Hashes元素: $AoH[0]{Husband} =“fred”; $AoH[1]{Husband}=~ s/(/w)//u$1/; # 打印所有数据 for $href ( @AoH ){ print “{ “; for $role (keys %$href ) { ??? print “$role=$href->{$role}“; } print “}/n”; } ? # 带指标打印 for $i ( 0 ..$#AoH ) { print “$i is { “; for $role ( keys %{ $AoH[$i] } ) { ??? print “$role=$AoH[$i]{$role}“; } print “}/n”; } 多维的Hash是Perl嵌套结构中最灵活的,应记住Hash的键不会以已知顺序存储。若需要输出,应使用sort函数。 产生Hash of Hashes : %HoH = ( ???? flintstones => { ???????? husband => "fred", ???????? pal????=> "barney", ???? }, ???? jetsons => { ???????? husbands? => "george", ????? ???wife?????=> "jane", ???????? "his boy" =>"elroy", ???? simpsons => { ???????? husband => "homer", ???????? wife???=> "marge", ???????? kid????=> "bart", ); ? # 从文件读入 # flintstones:lead=fred pal=barney wife=Wilma pet=dina while ( <> ){ ?? next unless s/^(.*?):/s*//; ?? $who = $1; ?? for $field ( split ) { ?????? ($key,$value) = split /=/,$field; ?????? $HoH{$who}{$key} = $value; } }? ? # calling a function? that returns a key,value hash
for $group ( "simpsons", "jetsons", "flintstones" ) { ???? $HoH{$group} = { get_family($group) }; } ? 使用和打印Hash ofHashes: # one element
$HoH{flintstones}{wife} = "wilma"; # another element $HoH{simpsons}{lead} =~ s/(/w)//u$1/; ?
# print the whole thing foreach $family ( keys %HoH ) { ???? print "$family: { "; ???? for $role ( keys %{ $HoH{$family} } ) { ???????? print "$role=$HoH{$family}{$role} "; ???? } ???? print "}/n"; } ?
# print the whole thing? somewhat sorted foreach $family ( sort keys %HoH ) { ???? print "$family: { "; ???? for $role ( sort keys %{ $HoH{$family} } ) { ???????? print "$role=$HoH{$family}{$role} "; ???? } ???? print "}/n"; } ?
# print the whole thing sorted by number of members foreach $family ( sort { keys %{$HoH{$b}} <=> keys %{$HoH{$a}} } keys %HoH ) { ???? print "$family: { "; ???? for $role ( sort keys %{ $HoH{$family} } ) { ???????? print "$role=$HoH{$family}{$role} "; ???? } ???? print "}/n"; } ?
# establish a sort order (rank) for each role $i = 0; for ( qw(lead wife son daughter pal pet) ) { $rank{$_} = ++$i } # now print the whole thing sorted by number of members foreach $family ( sort { keys %{ $HoH{$b} } <=> keys %{ $HoH{$a} } } keys %HoH ) { ???? print "$family: { "; ???? # and print these according to rank order ???? for $role ( sort { $rank{$a} <=> $rank{$b} }? keys %{ $HoH{$family} } ) { ???????? print "$role=$HoH{$family}{$role} "; ???? } ???? print "}/n"; } 有时候Hashes of Functions是有用的,例子如下: %HoF = ( ????? exit??=> sub { exit }, ????? help??=> /&show_help, ????? watch?=> sub {$watch = 1 }, ????? mail??=> sub { mail_msg($msg) }, ?? ???edit???=>sub {$edited++;?editmsg($msg); }, ????? delete?=> /&confirm_kill, ); ? if ($HoF{ lc $cmd} ) { $HoF{ lc $cmd}->() } else { warn “Uknowncommand: `$cmd`; Try `help` next time/n” } 操作符(Operators)概述从数学角度来看,操作符是特殊形式的普通函数;从语言学的角度来看,操作符是不规则动词,而各种数据类型则是名词。 根据带参数的多少,操作符可分为一元操作符(unary)、二元操作符(binary)、三元操作符(ternary)以及多元操作符(主要指list operators,可跟任何多参数)。 操作符的性质包括元数(arity)、优先级(precedence)和附着性(associativity)。附着性包括左附、右附和不附着。 操作符一览按照优先级从高到低,Perl的操作符列表如下: ?
各种操作符使用说明3.3.1项与左赋列表操作符项在Perl的优先级最高。项包括变量、引号操作符、在各种括号内的表达式和参数用括号括起来的函数。注意 chdir($foo) *20表示(chdir $foo)* 20, chdir +($foo) * 20表示 chdir ($foo * 20)。另外要注意括号的附着性,比如print $foo,exit可能不是你所需要的,但是print($foo),exit却达到目的!另外:print ($foo*255)+1,“/n”也不能达到打印($foo*255)+1的目的!所以一定要小心!如果有疑惑,可以加括号消除歧义。 3.3.2箭头操作符像C/C++一样,Perl的箭头操作符(->)指反引(dereference): 右边是[…]时,左边必须是数组引用;右边是{…}时,左边必须是Hash引用;左边是(…)时,左边必须是子程序引用或者一个对象(a blessreference)或类名(a packagename)。比如, $aref->[42]??????????????????? #an array dereference $href->{“cornedbeef”}????????? #a hash dereferenceu $sref->(1,3)????????????????? #a subroutine dereference $yogi =Bear->new(“Yogi”);????? #a classmethod call $yogi->swipe($picnic);????????? #an object method call 3.3.3自增自减Perl的++和--操作符与C的操作一致。但是Perl的++却有一个特殊的功能:当它对/^[a-zA-Z]*[0-9]*$/形式的字符串操作时,它是带进位的字符串自增,比如: ?print ++($foo = ‘99’);????#prints ‘100’ print ++($foo = ‘a9’); ????#prints‘b0’ print ++($foo = ‘Az’);???? #prints ‘Ba’ print ++($foo = ‘zz’);????? #prints ‘aaa’ ?3.3.4乘方C中没有乘方运算符,Perl的乘方运算符(**)是从FORTRAN中借鉴来的,使用C的pow(3)函数来实现。注意乘方运算符是右附的,所以-2**4是指-(2**4)而不是(-2)**4。 3.3.5表意一元操作符这些操作符基本都与求反相关: !是逻辑求反,相当于not,但not的优先级更低 - 如果操作数是数值则是算术负。注意如果操作数是一个标识符,将返回一个负号与标识符的连接值!(如– “abc”是”-abc”,- “-abc”是”+abc”) ~ 位求反。注意此操作是不可移植的。 ~123在32位机器与64位机器上结果是不同的! + 正号无论对数值与字符串都不起作用。但是正号有特别的作用是防止括号与函数名连接。如print?+($foo*255)+1,“/n”; / 对任何跟随的操作数生成一个引用 3.3.6捆绑操作符这些操作将一个字符表达式与模式匹配、替换或转换捆绑,否则这些操作将作用在$_变量上。 注意$_ =~ $pat等价于$_ =~ /$pat/,$string !~ /pattern/等价于 not $string =~ /pattern/。如果操作作用在替换上,将返回替换的次数,一般返回是否匹配成功。 3.3.7乘操作符*(乘)、/(除)、%(求模)的基本行为与C一致。关于此类操作符需说明以下几点: l????????/默认是进行浮点计算,除非use integer(这一点与C不同!)。即$i=23; print $i/2不是打印12,而是11.5。 l????????%将在求模前将操作数转换为整数。 l????????x是Perl特有的重复操作符!非常实用!如print ‘-‘ x 80;将打印一行-;@ones=(5) x @one将所有@ones的元素设为5。 3.3.8加操作符注意事项: l????????Perl的+(加)-(减)操作符若作用在字符串上,会将字符串转化为数值! l????????Perl有另外的.操作符进行字符串连接。Perl不会在字符串之间加空格。如果有多个字符串需要连接,可以使用join函数。 l????????Perl的字符串连接操作符类似AWK的空格。其+-将字符串转化为数值也是从AWK借鉴来的。 3.3.9移位操作符左移(<<)右移(>>)操作符行为与C一致,操作数必须为整数,注意结果依赖于机器表示整数的位数。 3.3.9有名一元和文件测试操作符Perl的有些函数实际是一元操作符,这些函数包括:
? 文件测试操作符及其意义如下:
使用注意事项: l????????一元有名操作符的默认参数一般为$_,next iflength < 80必须用next iflength() < 80; l????????一元有名操作符的优先级比一些二元操作符高,所以注意sleep 4 | 3的意义; l????????文件测试时_表示延续上一测试(包括stat函数)的文件,比如print “Can do./n” if –r $a|| -w _ || -x _; 3.3.10关系操作符应注意Perl有两套比较操作符,针对数值与针对字符串的,两者不可混用!这是Perl相对于C特别之处!Perl的关系操作符如下:
另外需注意:后三套操作符的优先级比前四套低;还有<=>和cmp是Perl特有的关系操作符,如果左操作数比右操作数小它返回-1,如果相等返回0,如果左操作数大于右操作数返回+1。<=>操作符被称为“宇宙飞船”操作符(spaceship operator)。 3.3.11位操作符Perl的位操作符&(AND)、|(OR)、^(XOR)针对数值与字符串时有不同操作:如果任一操作数是数,两个操作数都被转化为整数;如果两个操作数都是字符串,对这个字符串的每一位做位操作。如: print “12345” & “23456”????#prints 02044 print 12345 &23456;??????? #prints 4128 3.3.12 C风格逻辑操作符Perl提供与C类似的&&(logical AND)和||(logical OR)操作符。但是应注意:Perl的&&与||的返回值的方式与C不同:它不是返回0或1,而是返回最后一个计算的值! 所以可以通过如下方式赋值: $home = $ENV{HOME}|| $ENV{LOGDIR} || (getpwuid($<))[7] || die “You’are homeless!/n”; $and = “abc”&& “def”? ;????#$and值为def $or = “abc” || “def”;?????????? #$or 值为abc 3.3.13范围操作符范围操作符..实际是两个完全不同的操作符,针对不同环境意义不同: l????????在标量环境中,返回一个Boolean值,它模拟sed和awk的逗号操作符: if(2..10) {print;} l????????在标量环境,(3..n)返回1、2、…n-2E0,最后一个量附加了额外的“E0”。 l????????..与…的差别是…操作符在比较左值为真后不对右值进行比较:所以if(3..3) {print:}将不打印任何行,而if(3…3){print;}将打印第3行之后的所有行。 l????????在列表(List)环境中,返回从左值到右值的一个列表,注意左右值可以是数值也可以是字符串,如(‘aa’..’zz’)或(2..10)。 3.3.14条件操作符条件操作符的格式是:COND ? THEN : ELSE 比如,以下语句是很常见的: printf “I have %dcamel%s./n” $n,$==1 ? ”” : “s”; 另外,注意条件操作符的优先级。下面语句可以判别某一年是否闰年: $leapyear = $year % 4 == 0 ? ? $year % 100 ==0 ??? ? $year % 400 == 0 ???????? ? 1 ???????? : 0 ???? :1 ?? :0; $leapyear = $year % 4 ? 0: $year % 100 ? 1: $year % 400? 0:1; 3.3.14赋值操作符Perl提供C的所有的赋值操作符,同时增加了一些自己特有的。有很多赋值操作符,如 **= x= %=&&= ||=? += -= ^= TARGET OP= EXPR 等价于 TARGET = TARGET OP EXPR; 注意事项: l????????TARGET只求值一次,所以$var[$a++] += $value中$a只增一次 l????????与C不同的是,Perl的赋值表达式产生一个有效的左值(lvalue)。 比如:($a += 2) *= 3;等价于$a += 2; $a *= 3; 这个语句也很常见:($new = $old) =~ s/foo/bar/g; 3.3.15逗号操作符注在标量环境中,它计算左边的所有值,扔掉,返回最右边的值。注意以下语句结果的不同: $a = (1,3);?? ??#$a=3 ($a) = (1,3)???? #$a=1 @a=(1,3)????? #@a=qw/1 3/; =>大多数时候只是逗号操作符的同义操作符。列表操作符的优先级比逗号操作符低。 3.3.16逻辑and,or,not和xor操作符这四个逻辑操作符比相应C操作符优先级低。所以 unlink “alpha”,“beta”,“gamma” or gripe(),next LINE;与 unlink(“alpha”,“gamma”) || (gripe(),next LINE);等价。 但应注意or与||不总能互换,比如: $xyz = $x || $y ||$z; 与 $xyz = $x or$y or $z;意义是不同的! xor操作在C与Perl中都没有对应,因为左右两边都会计算,不会short-circuit。 ? 与C操作符的比较3.4.1 Perl操作符的特别之处l????????乘方(**)、比较(<=>)、模式匹配(=~ !~)、字符串连接(.)、区域操作( .. …)C没有; l????????Perl对数值和字符串有两套不同的关系(比较)操作符,应注意区分。 3.4.1 C有Perl没有的操作符Perl没有C的如下操作符:取址操作符(&)、指针操作符(*,用来dereference)、类型转换操作符((TYPE))。 说明:Perl没有地址,所以不需要dereference一个地址,它的确有引用,它用$、@、%、&来反引。Perl也有*符号,也用表示一个typeglob。 Perl程序由一系列声明(Declaration)与语句(Statement)构成。声明主要在编译期间起作用。Perl不要求变量显式声明,它在初次使用时自动生成,如果未被赋值,在数值环境中会被作为0,在字符串环境中会被当成””,在作为逻辑变量时将作为false。但是Perl一般使用use strict;防止使用未定义变量。 语句按复杂程度可分为简单语句、复合语句,按逻辑关系可分为条件语句、循环语句等。 简单语句简单语句必须以分号结束,除非它是一个块的最后语句。一个简单语句可以加以下修饰符: if EXPR unless EXPR while EXPR until EXPR foreach LIST 比如: $trash->take(‘out’)if $you_love_me; shutup() unless$you_want_me_to_leave; kiss(‘me’) until$I_die; $expression++while –e “$file$expression”; s/java/perl/ for@resumes; print “field: $_/n”foreach split /:/,$dataline; ? 注意事项: l????????Perl的if、unless语句正常使用时后面必须是BLOCK,哪怕只有一句话,也必须加{ },这对于C程序员会觉得很别扭,但是Perl也提供了替代方案,就是将if、unless作为后修饰符。 l????????Perl的if与unless,while与until完全起相反的功能:unless EXPR等价于if (! EXPR), until EXPR等价于 while (!EXPR)。 l????????Perl的until语句与C的do{ }until语句的意义是不同的!Perl的until是与while相反的。until(EXPR)相当于while (!EXPR)。先测试后执行,而不是无条件执行。以后两个例子的输出是完全不同的: $i = 0;?? do{ print “abc”}until ($i == 0);??????????#会输出abc $i= 0;??? print “abc”until($==0);????????????????#不会输出abc 即until在没有do时在最开始也会对条件进行判断,以决定是否执行前面的语句。 复合语句在一个范围内一系列语句称为一个块(BLOCK),复合语句是由表达式与BLOCKs组成,表达式由项与操作符组成。复合语句又可以分为复合条件语句与复合循环语句。 4.2.1条件语句(if/unless语句)条件语句的语句如下: if (EXPR) BLOCK if (EXPR) BLOCKelse BLOCK if (EXPR) BLOCKelsif (EXPR) BLOCK if (EXPR) BLOCKelsif (EXPR) BLOCK … else BLOCK ? unless (EXPR)BLOCK unless (EXPR)BLOCK else BLOCK unless (EXPR)BLOCK elsif (EXPR) BLOCK unless (EXPR)BLOCK elsif (EXPR) BLOCK … else BLOCK ? 说明: l????????if与unless是互补的,unless ($x == 1)等价于 if($x != 1)或者 if (! ($x == 1))。 l????????if/unless语句后面都是跟BLOCK,即必须有{ },哪怕只有一条语句!这是Perl很特别的地方。但是有不用括号的方法,即使用简单if/unless语句。 l????????注意Perl使用elsif,试比较C之else if,好像是缺了一个字母似地! l????????变量声明的范围从声明开始之处到所有本条件语句之内,包括elsif和else在内。如: if ((my $color =<STDIN> ) =~ /red/i) { ?????? $value = 0xff0000; } elsif ($color =~/green/i) { ???? $value = 0x00ff00; } else{ ???? warn “Unknown RGB component/n”; ???? $value = 0x000000; } 语句)循环语句的语句如下: LABEL while (EXPR)BLOCK LABEL while (EXPR)BLOCK continue BLOCK ? LABEL until (EXPR)BLOCK LABEL until (EXPR)BLOCK continue BLOCK ? LABEL for (EXPR:EXPR: EXPR) BLOCK ? LABEL foreach(LIST) BLOCK LABEL foreach VAR(LIST) BLOCK LABEL foreach VAR(LIST) BLOCK continue BLOCK ? 例如: LINE: while(<STDIN>) { next LINE if /^#/;???????? #skip comments next LINE if /^$/;???????? #skip blank lines … } continue { $count++; } ? LINE: while(defined($line = <ARGV>)) { chomp($line); if ($line =~ s///$//) { ??? $line .= <ARGV>; ??? redo LINE unlesSEOf(ARGV); } # now process $line } ? 说明: l????????while/until的进行的判断是互补的,until也是先判断再决定是否执行语句。 l????????LABEL是可选的,但是在Perl实践中还是经常使用,特别是循环内有next/last/redo语句的时候。 l????????注意for和foreach关键字在实践上可以互换!但两者概念上是不同的。但是系统可以分别是for还是foreach操作。 l????????foreach是直接对数组元素操作的,变量是真正数组元素的别名,这一点一定要清楚。比如@arr=(1,3,4,5);foreach $i(@arr){ $i++} @arr就变成了(2,3,4,5,6)。 l????????循环控制操作符如下: last?? LABEL? (相当于C的break) next? LABEL? (相当于C的continue) redo? LABEL LABEL是可选的,如果省略,表示最内层循环。 ??? 应明白: redo/last后不执行continue BLOCK。 l????????在结构化编程的最初阶段,有些人坚持循环与子程序只能有一个入口和一个出口。One-entry是一个好的思想,但是one-exit通常是不太现实的,所以Perl建议按照需要退出循环。 l????????last/redo/next可以用于BLOCK,但是eval{},sub{},do{}却不属于循环BLOCK,同样if/unless中也不能直接使用。要使用的话,必须再加一层{}。例如: if (/pattern/) {{ last if /alpha/; last if /beta/; last if /gamma/; # do something her only if still in if() }} ? do {{ ? ??next if?$x == $y; ??? #do something here }} until $x++ > $z; ? { ??? do{ ???????last if $x = $y ** 2; ???????# do something here ??? }while $x++ <= $z; } 注意:如果仍然用 do {{ last if $x = $y ** 2}} where $x++ <= $z;那么last起不到作用!后面的while继续运行。????????????????????????? Perl没有正式的switch和case语句。这是因为Perl并不需要,可以用以下方式达到同样目的: SWITCH: { ???? if?(/^abc/) { $abc = 1; last SWITCH; } ???? if?(/^def/) { $def = 1; last SWITCH; } ???? if?(/^xyz/) { $xyz = 1; last SWITCH; } ???? $nothing = 1; } ? SWITCH: { ???? /^abc/ && do { $abc = 1; lastSWITCH; } ???? /^def/ && do { $def = 1; lastSWITCH; } ???? /^xyz/ && do { $xyz = 1; lastSWITCH; } ???? $nothing = 1; } 子程序简介子程序又称为函数。在Perl里子程序与函数是同一个概念。Perl允许用户自定义子程序,它们可以在主程序的任何位置,从其它文件中通过do/require/use关键字引入,或者使用eval在运行时生成,或者通过无名子程序的引用使用。 Perl的输入、输出模型非常简单:所有的输入的参数都是转化为一个标量的List,所有的函数都返回一个List列表。Perl中,数组变量@_是一个特殊的系统变量。如果函数调用时后面跟着一个用括号括起来的列表,则在函数调用期间该列表将被自动分配给一个以@_命名的特殊变量。@_是一个局部变量,它的值只在它出现的函数中有定义。return用来返回参数,如果return后面不带参数,在List环境中将返回空列表,在标量环境中将返回undef,在Boolean环境中将返回void。 声明子程序: ??? sub NAME;???????????????????? # A "forward" declaration. ??? sub NAME(PROTO);????????????? #? ditto,but with prototypes ??? sub NAME : ATTRS;???????????? #? with attributes sub NAME(PROTO) : ATTRS;????? #? with attributes and prototypes 定义子程序:
??? sub NAME BLOCK??????????????? # A declaration and a definition.
??? sub NAME(PROTO) BLOCK???????? #? ditto,but with prototypes
??? sub NAME : ATTRS BLOCK??????? #? with attributes
??? sub NAME(PROTO) : ATTRS BLOCK #? with prototypes and attributes
定义无名子程序: ??? $subref = sub BLOCK;???????????????? # no proto ??? $subref = sub (PROTO) BLOCK;???????? # with proto ??? $subref = sub : ATTRS BLOCK;???????? # with attributes ??? $subref = sub (PROTO) : ATTRS BLOCK; # with proto and attributes 从模块中引入子程序: ??? use MODULE qw(NAME1 NAME2 NAME3); 使用子程序: ??? NAME(LIST);??? # & is optional with parentheses. ??? NAME LIST;???? # Parentheses optional if predeclared/imported. ??? &NAME(LIST);?? # Circumvent prototypes. &NAME;???????? # Makes current @_ visible to called subroutine. ?
&$subref(LIST)
$subref->(LIST)
&$subref
注意:有带&与不带&的使用方式,Perl5后建议不用&。但是若NAME不带参数不带任何参数时,及需要将通过函数引用使用函数时(?
sub max { ??????? my $max = shift(@_); ??????? foreach $foo (@_) { ????? ??????$max = $foo if $max < $foo; ??????? } ??????? return $max; ??? } ?
$bestday = max($mon,$tue,$wed,$thu,$fri); ? @common = inter( /%foo,/%bar,/%joe ); sub inter { ?? my %seen; ?? for my$href? (@_) { ??????? while(my $k = each %$href ) { ??????????? $seen{$k}++; ???????? } ?? } ?? return grep {$seen{$_} == @_ } keys %seen; } 函数原型与属性Perl有限地允许使用函数原型,函数原型的使用说明如下: l????????函数原型只有在&字符忽略的时候影响函数的解释; l????????反斜杠(/)原型字符代表一个实际的参数; l????????分号(;)分割必选参数与可选参数; l????????*允许子程序接受文件句柄作为参数 使用函数原型的例子如下:
Perl函数也可以定义属性,三个标准属性如下: l????????locked :只允许一个进程进入 l????????method:表明函数是一个方法。等价于useattributes__PACKAGE__,/&foo,'method'; #foo为函数名 l????????lvalue:?允许函数作为左值使用 内部函数按函数的分类,Perl提供的主要内部函数如下:
文件、目录与I/O句柄(Handle)是Perl得到数据的一种方法。Perl提供两种句柄:文件句柄(File Handles)与目录句柄(Directory Handles),另外提供三个默认的句柄:标准输入(STDIN)、标准输出(STDOUT)与标准错误(STDERR). 文件操作文件的打开的格式为: open FILEHANDLE,MODE,LIST
open FILEHANDLE,EXPR
open FILEHANDLE
实例: open(INFO,????? "datafile")? || die("can't open datafile: $!"); open(INFO,??? "< datafile")?|| die("can't open datafile: $!"); open(RESULTS,"> runstats")?||die("can't open runstats: $!"); open(LOG,??? ">> logfile ")?|| die("can't open logfile:?$!"); open INFO,????? "datafile"?? or die "can't open datafile: $!"; open INFO,??? "< datafile"??or die "can't open datafile: $!"; open RESULTS,"> runstats"??or die"can't open runstats: $!"; open LOG,??? ">> logfile "??or die "can't open logfile:?$!"; open(INPUT,?"-" ) ordie;????# re-open standard input forreading open(INPUT,? "<-") or die;????# same thing,but explicit open(OUTPUT,">-") or die;????# re-openstandard output for writing open(PRINTER,"| lpr -Plp1")?? ?or die "can't fork: $!";
print PRINTER "stuff/n";
close(PRINTER)????????????????? or die "lpr/close failed: $?/$!";
? ? 文件的打开模式的意义如下:
文件的关闭: close FILEHANDLE 文件的删除: unlink 可以从目录中删除文件 如: unlink <*.c>;? unlinkglob "*.o"; 文件的重命名: rename oldname newname 文本文件的读写: open(LOG,? “/log/logfile”) while(<LOG>) { … chomp; ?? …… } 目录操作Perl的目录句柄与文件句柄有不同的命名空间,改变目录的方式为: ? chdir "/etc" ??or die "cannot chdir to /etc: $!";
读取目录文件有几种方式:使用glob,<>或者readdir:
my @all_files = glob "*";
my @pm_files = glob "*.pm";
my @files = <FRED/*>;? ## a glob
opendir(THISDIR,“.”)??????????? or die “Cannot open currentdirectory $!”; @allfiles = readdir THISDIR; @allfiles = grep –T,readdir THISDIR; @allfiles = grep { $_ ne ‘.’ And $_ ne ‘..’} readdir THISDIR close THISDIR; print “@allfiles/n”; ? ? Perl有两个主要的输出函数:print与printf,其使用格式为: print FILEHANDLE LIST
print LIST
print
printf FILEHANDLE FORMAT,LIST
printf FORMAT,LIST
(相当于print FILEHANDLE sprintf(FORMAT,LIST) ?
例如:
print { $OK ? "STDOUT" : "STDERR" } "stuff/n";
print { $iohandle[$i] } "stuff/n";
print {$fh}? @array1,@array2,"/n";
注意事项l????????有很多种打开文件的方式: open FH,“<$filename”????? or?die “Cannot open filename $filename $!”;?????(1) open(FH,“<$filename”)???? ||?die “Cannot open filename $filename $!”;??????(2) open(FH,‘<’,$filename)???? ||? die “Cannot open filename $filename $!”;?? ????(3) open(my $fh,$filename)??? ||? die “Cannot open filename $filename $!”;?? (4) open my $fh,$filename??? or? die “Cannot open filename $filename $!”;?? (5) open(my $fh,$filename)?? or? die “Cannot open filename $filename $!”;?? (6) 建议使用方式(4)、(5)或(6)。注意||和or与前面的open语句最好有相当大的间隔,以表明此语句是与open分离的。裸词存在于当前包的符号表中,若此前该文件句柄已经存在,Perl将无声地代替以前的文件句柄(很危险);而两个参数的打开方式不能很好地处理以’<’’>’开头的文件名。唯一需要两个参数的打开方式的时候是打开标准I/O流(三参数方式此时无效): open my $stdin,‘<-‘????????? ?or croak “Cannot open stdin; $OS_ERROR”; open my $stdout,‘>-‘????????? or croak “Cannot open stdout;$OS_ERROR”; l????????open FH,“$filename”等价于open FH,“<$filename”。 l????????以下打开方式是错误的: open my $fh,?‘<’,?$filename????||?die “Cannot open filename $filename $!”;?? 因为||的优先级比List分隔符(,)高,所以||会附在$filename上。以上方式(6)中的括号虽然是多余的,但是对于初学者更有可读性。 l????????使用引用来作为句柄,print时应养成加括号的习惯,即print {$fh} LIST,这样可以避免将$fh作为输出LIST一部分的错误。 l????????输出函数print默认在LIST之间不会加空格或其它符号,在最后也不会自动加回车符。这与AWK与很大的不同(默认OFS为空格,ORS为回车),有时候会觉得使用不方便(如需要输出CSV格式文件时),这是可以通过以下语句设置LIST和记录分割符: local? $,”; local? $/ = “/n”; 或者可读性更好的方式: use English; local? $OFS = “,”; local? $ORS = “/n”; Perl语言的最大特点,也是Perl作为CGI首选语言的最大特点,是它的模式匹配操作符。Perl语言的强大的文本处理能力正是通过其内嵌的对模式匹配的支持体现的。模式通过创建正则表达式实现。Perl的正则表达式与模式匹配的特点一是内嵌于语言之中,而不是通过库或函数来实现,因此使用更简便;二是比一般的正则表达式与模式匹配功能强大。 模式匹配操作符简介
使用说明: l????????注意区别记忆Perl的绑定操作符(=~)与AWK的相应操作符(AWK的绑定匹配操作符是 ~),Perl与AWK的否定匹配操作符相同(都是!~) l????????没有绑定操作符时,默认是对$_进行绑定: /new life/ and /new civilizations/ (对$_进行两次查找) s/suger/aspartame/? (对$_进行替换) tr/ATCG/TAGC/??? (对$_进行转换) l????????m//操作符前面的m可以省略,但是不省略可读性更好,建议不省略。 l????????如果有绑定操作符=~,m//都省略也表示匹配: print “matches” if $somestring =~ $somepattern;等价于 print “matches” if $somestring =~ m/$somepattern/; l????????m//,tr///,qr//操作符是引用操作符,你可以选择自己的分割符(与q//,qw//一样): $path =~ s#/tmp#/var/tmp/scratch# if? ($dir =~ m[/bin]) { ??? print “No binary directoriesplease. /n”; } l????????一个括号可与其它括号配合使用,可以用空格分开: s(egg)<larva> s(larva){pupa}; s[pupa]/imago/; s (egg) <larva>;???? l????????如果一个模式成功匹配上,$`,$&,$’将被设置,分别表示匹配左边、匹配、匹配右边的字符串: “hot cross buns” =~ /cross/; print “Matched: <$`> $& <$’>/n”;?? # Matched <hot > cross < buns> l????????模式模式后设置的特殊变量如下:
? m//,s///和qr//都接受以下修饰符:
例如: m//w+:(/s+/w+)/s*/d+/;????????? # A word,colon,space,word,digits ? m//w+: (/s+? /w+) /s* /d+/x;????# A word,digits ? m{ /w+;???????????????????? #Match a word and a column (??????????????????????? #(begin group) ??? /s+?????????????????# Match one or more spaces. ??? /w+????????????????# Match another word )??????????????????????? #(end group) /s*????????????????????? #Match zero or more spaces /d+????????????????????? #Match some digits }x; ? $/ ="";???? # "paragrep"mode ? while (<>) { ??? while ( m{ ????????????? ?/b??????????# start at a word boundary ????????????? ?(/w/S+)?????# find a wordish chunk ????????????? ?( ????????????? ???? /s+?????# separated by some whitespace ????????????? ???? /1??????# and that chunk again ????????????? ?) +?????????# repeat ad lib ????????????? ?/b??????????# until another word word boundary ?????? ??}xig ?) ?{ ?????? ???print "dup word '$1' at paragraph $./n"; ??? } ?} 模式匹配操作符详解7.3.1 m//操作符(匹配)EXPR =~ m/PATTERN/cgimosx EXPR =~ /PATTERN/cgimosx EXPR =~ ?PATTERN?cgimosx m/PATTERN/cgimosx /PATTERN/cgimosx ?PATTERN?cgimosx 说明: l????????如果PATTERN是空字符串,最后成功执行的正则表达式将被代替使用。 l????????m//特殊修饰符如下:
l????????在LIST上下文中m//g返回所有匹配 if (@perls = $paragraph =~ /perl/gi) { ???printf “Perl mentioned %d times./n”,scalar @perls; } l??????????分隔符表示一次性匹配,‘’分隔符压制变量替换和/U等六个转换 open DICT,"/usr/share/dict/words" ?or die"Cannot open words: $!/n"; while (<DICT>) { ???$first = $1 if ?(^love.*)?; ???$last? = $1 if /(^love.*)/; } print $first,"/n"; print $last,"/n"; 7.3.2 s///操作符(替换)LVALUE =~ s/PATTERN/REPLACEMENT/egimosx s/PATTERN/REPLACEMENT/egimosx 说明: l????????该操作符在字符串中查找PATTERN,如果查找到,用REPLACEMENT代替匹配的子串。返回值是成功替换的次数(加/g修饰符可能大于1)。若失败,返回””(0)。 ?if($lotr =~ s/Bilbo/Frodo/) { print “Successfully wrote sequel. “ } ?$change_count = $lotr =~ s/Bilbo/Frodo/g; l????????替换部分作为双引字符串,可以使用动态生成的模式变量($`,$&,$’,$1,$2等): s/revision/version/release//u$&/g; s/version([0-9.]+)/the $Names{$1} release/g; l????????如果PATTERN是空字符串,最后成功执行的正则表达式将被代替使用。PATTERN和REPLACEMENT都需进行变量替换,但是PATTERN在s///作为一个整体处理的时候替换,而REPLACEMENT在每次模式匹配到时替换。 l????????s///特殊修饰符如下:
/e修饰符的实例: s/[0-9]+/sprintf(“%#x”,$1)/ge s{ ?? version ?? /s+ ( ?[0-9.]+ ) }{ ?? $Names{$1} ????? ? “the $Names{$1} release” ????? : $& }xge; l????????不替换原字符串的方式: $lotr = $hobbit; $lotr =~s/Bilbo/Frodo/g; ($lotr = $hobbit)=~ s/Bilbo/Frodo/g; l????????替换数组中的每一元素: for (@chapters) { s/Bilbo/Frodo/g } s/Bilbo/Frodo/g for @chapters; l????????对某一字符串进行多次替换: for ($string) { s/^/s+//; s//s+$//; s//s+/ /g } for ($newshow = $oldshow) { ???s/Fred/Homer/g; ???s/Wilma/Marge/g; ???s/Pebbles/Lisa/g; ???s/Dino/Bart/g; } l????????当一次全局替换不够的时的替换: ?# put comma in the right places in an integer ?1 while s/(/d)(/d/d/d)(?!/d)/$1,$2/; ?# expand tabs to 8-column spacing ?1 while s//t+/’ ‘ x (length($&)*8 –length($`)%8)/e; ?# remove (nested (even deeply nested (likethis))) remarks ?1 while s//([^()]*/)//g; ?# remove duplicate words (and triplicate ( andquadruplicate…)) ?1 while s//b(/w+) /1/b/$1/gi; 7.3.3 tr///操作符(字译)LVALUE =~tr/SEARCHLIST/REPLACELIST/cds tr/SEARCHLIST/REPLACELIST/cds 使用说明: l????????tr///的修饰符如下:
l????????如果使用了/d修饰符,REPLACEMENTLIST总是解释为明白写出的字符串,否则,如果REPLACEMENTLIST比SEARCHLIST短,最后的字符将被复制直到足够长,如果REPLACEMENTLIST为空,等价于SEARCHLIST,这种用法在想对字符进行统计而不改变时有用,在用/s修饰符压扁字符时有用。 tr/aeiou/!/;??????? # changeany vowel into ! tr{////r/n/b/f. }{_};? #change strange chars into an underscore tr/A-Z/a-z/ for @ARGV;?? #canonicalize to lowercase ASCII $count = ($para =~ tr//n//); $count = tr/0-9//; $word =~ tr/a-zA-Z//s;???? #bookkeeper -> bokeper tr/@$%*//d;???????????? #delete any of those tr#A-Za-z0-9+/##cd;????? #remove non-base64 chars # change en passant ($HOST = $host) =~ tr/a-z/A-Z/; $pathname =~ tr/a-zA-Z/_/cs;?# change non-(ASCII) alphas to single underbar 元字符Perl元字符有: / | ( ) [ { ^ $ *+ ? 正则表达式元字符的意义如下:
? * + ?是数量元字符,Perl数量相关元字符意义如下:
扩展正则表达式序列如下:
说明:以上定义了向前查找(?=PATTERN),负向前查找(?!PATTERN),向后查找(?<=PATTERN),负向后查找(?<!PATTERN),条件查找等较为高级的正则表达式匹配功能,需要使用时请查阅相关资料。 字母顺序元字符意义:
(以上均直接Copy自《ProgrammingPerl》,下面未翻译者同) 其中应注意以下经典的字符集合:
POSIX风格的字符类如下:
注意:POSIX风格字符类的使用方法, 42 =~/^[[:digit:]]+$/? (正确) 42 =~/^[:digit:]$/? ?(错误) 这里使用的模式以[[开头,以]]结束,这是使用POSIX字符类的正确使用方法。我们使用的字符类是[:digit:]。外层的[]用来定义一个字符集合,内层的[]字符是POSIX字符类的组成部分。 常见问题的正则解决方案IP地址: (((/d{1,2})|(1/d{2})|(2[0-4]/d)|(25[0-5]))/.){3}((/d{1,2})|(1/d{2})|(2[0-4]/d)|(25[0-5])) 邮件地址: (/w+/.)*/w+@(/w+/.)+[A-Za-z]+ (以上邮件地址正则表达式并非严格的,但是可以匹配绝大多数普通的邮件地址。 HTTP URL: {http://([^/:]+)(:(/d+))?(/.*)?$}i https?://(/w*:/w*@)?[-/w.]+(:/d+)?(/([/w/_.]*(/?/S+)?)?)? C语言注释: //*.*?/*/ 在Perl中,类、包、模块是相关的,一个模块只是以同样文件名(带.pm后缀)的一个包;一个类就是一个包;一个对象是一个引用;一个方法就是一个子程序。这里只说明其最简单的使用方法。 模块使用以下是一个模块(Bestiary.pm)的编写方式,可以作为写一般模块的参考。 package????? Bestiary;
require????? Exporter;
?
our @ISA?????? = qw(Exporter);
our @EXPORT??? = qw(camel);??? # Symbols to be exported by default
our @EXPORT_OK = qw($weight);? # Symbols to be exported on request
our $VERSION?? = 1.00;???????? # Version number
?
### Include your variables and functions here
?
sub camel { print "One-hump dromedary" }
?
$weight = 1024;
?
1;
(引自《Programming Perl》)
对象使用以下例子用来构建一个Ipregion对象,可以使用该对象的get_area_isp_id方法查找一个IP的地区与运营商。本例可以作为写一般对象的参考。 package Ipregion; use strict; ? my ($DEFAULT_AREA_ID,$DEFAULT_ISP_ID) =(999999,9); my ($START_IP,$END_IP,$AREA_ID,$ISP_ID)= (0 .. 3);??? sub new { ????my $invocant = shift; ????my $ip_region_file = shift; ????my $class = ref($invocant) || $invocant; ????my $self = [ ];????????????????????# $self is an reference of array of arrays ? ????# Read into ip region data from file ????open my $fh_ip_region,'<',$ip_region_file??? or? die "Cannot open$ip_region_file to load ip region data $!"; ? ????my $i = 0; ????while (<$fh_ip_region>) { ?????? ???? chomp; ?????? ???? my ($start_ip,$end_ip,$area_id,$isp_id)= split; ?????? ???? $self->[$i++] = [ $start_ip,$isp_id ]; ????} ? ????bless($self,$class); ????return $self; } ? sub get_area_isp_id { ???my $self ?????? = shift; ???my $ip ???????? = shift; ???my $area_id = $DEFAULT_AREA_ID; ???my $isp_id ??? = $DEFAULT_ISP_ID; ? ??? #Check if a ip address is in the table using binary search method. ???my $left? = 0;????????????????? ???my $right?????? = @$self - 1;?????????????????? # Get max array index?? ???my $middle;?????????????????? ? ???while ($left <= $right) { ???????$middle = int( ($left + $right) / 2 ); ??????? if (($self->[$middle][$START_IP] <= $ip) && ($ip <=$self->[$middle][$END_IP]) ) { ???? ???????$area_id =$self->[$middle][$AREA_ID]; ???????????$isp_id? =$self->[$middle][$ISP_ID]; ???????????last; ???????} ???????elsif ($ip < $self->[$middle][$START_IP]) { ???????????$right = $middle - 1; ???????} ???????else { ???????????$left = $middle + 1; ???????} ??? } ? ???return ($area_id,$isp_id); } ? 该对象的使用方法是: use Ipregion; my $ip_region =Ipregion->new("new_ip_region.dat"); my @search_result =$ip_region->get_area_isp_id(974173694); ? .Perl特殊变量
说明:若需要使用长文件名,必须使用use English; 程序文档(POD)POD(Plain Old Documentation), 它是一种简单而易用的标记型语言(置标语言),用于perl程序和模块中的文档书写。POD中用段分可以分为三种,普通段落,字面段落(Verbatim Paragraph)和命令段落。三者的区分非常简单,以=pod|head1|cut|over等指示字开始的段落为命令段落,以空格或制表符(/t)等缩进开始的段落为字面段落,其余的就是普通段落。POD中有其独特的格式代码来表现粗体,斜体,超链接等。 POD使得Perl语言的文档编写易于完成,程序说明文档与程序源代码同时存在。可以用以下解释器解释POD:pod2text、pod2man (pod2man File.pm |nroff –man |more)、pod2html、pod2latex。 一般建议对源代码包括以下部分的POD文档: =head1 NAME The name of your program or module. =head1 SYNOPSIS A one-line description of what yourprogram or module does (purportedly). =head1 DESCRIPTION The bulk of your documentation. (Bulk isgood in this context.) =head1 AUTHOR Who you are. (Or an alias,if you areashamed of your program.) =head1 BUGS What you did wrong (and why it wasn'treally your fault). =head1 SEE ALSO Where people can find related information(so they can work around your bugs). =head1 COPYRIGHT The copyright statement. If you wish toassert an explicit copyright,you should say something like: Copyright 2013,Randy Waterhouse.?All Rights Reserved. Manymodules also add: This program is free software.?You may copy or redistribute it under the same terms asPerl itself. 编程风格为了使程序易于阅读、理解和维护,建议使用以下编程风格(以下建议均为LarryWall在perlstyle文档中所写,其实许多条对于其它语言编程也适用): l????????多行BLOCK的收尾括号应该跟结构开始的关键字对齐; l????????4列的缩进; l????????开始括号与关键字同一行,如果可能的话,否则,与关键字对齐; l????????在多行BLOCK的开始括号之前留空格; l????????一行的BLOCK可以写在一行中,包括括号; l????????在分号前不留空格; l????????分号在短的一行BLOCK中省略; l????????在大多数操作符两边留空格; l????????在复杂的下标两边加空格(在方括号内); l????????在做不同事情的代码段中留空行; l????????不连接在一起的else; l????????在函数名和开始的括号之间不加空格; l????????在每一逗号后加空格; l????????长行在操作符后断开(除了and和or外) l????????在本行匹配的最后的括号后加空格 l????????相应项竖直对齐 l????????只要清晰性不受损害省略冗余的标点符号 l????????你能怎样做不意味着你应该怎样做。Perl设计来给予你做任何事的多种方法,所以选择最易读的方法。 open(FOO,$foo) || die “Cannot open $foo: $!”; 比 die “Cann’t open $foo: $!” u;nlessopen(FOO,$foo) l????????不要害怕使用循环标号,它们可以增强可读性并且允许多层循环跳出。 l????????选择易于理解的标示符。如果你不能记住你的记忆法,你将碰到问题 l????????在使用长标示符时用下划线分隔单词 l????????在使用复杂的正则表达式时使用/x l????????使用here文档而不是重复的print()语句 l????????垂直对齐相应的东西,特别是在一行不能容下时 l????????总是检查系统调用的返回值。好的错误信息应该输出的标准错误输出,包括那一程序造成了问题的信息。 l????????对齐转换的信息: ????? tr [abc] ??????? [xyz] l????????考虑复用性,一般化你的代码,考虑写一个模块或者类。 l????????使用POD文档化你的代码 l????????保持一致 l????????保持友好 参考文献【1】Larry Wall,Tom Christiansen & Jon Orwant. Programming Perl. Third Edition (The CamelBook) 【2】Randal L.Schwartz,Tom Phoenix,and brian d foy. Learning Perl (The Llama Book ) 【3】DamianConway. Perl Best Practices(Perl最佳实践—影印版). O’reilly.东南大学出版社.2006.4 【4】Ben Forta著.杨涛等译.正则表达式必知必会.北京:人民邮电出版社. 2007.12 【5】Jeffrey E.F.Friedl著.余晟译.精通正则表达式(第3版).北京:电子工业出版社. 2007.7 【6】The perldocmainpage ? ? http://fayland.org/journal/POD.html (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |