不行,引用 Chaim Frenkel的话:“Perl的语法无法被简化到可以用BNF 表示。解析Perl的工作是分散於 yacc、lexer、烟雾和镜子之间。”
他们都是指定形态 (type)用的符号,如同perldata里所详述的:
$纯量值 (scalar) (数字,字串或参考值 [reference]) @阵列 %杂凑阵列 (关连阵列) *代表同一个变数名的所有类形。在第四版中它们常用来达到指标 (pointers)的功能,但现在在新版的 perl中这个角色已被参 考值 (reference)取代了。
虽然这些符号在某些场合下可省略,但建议你随处都用。
有些其他的符号你可能会碰到但却不是指定形态用的有:
<>这是用来从一个档案把手 (filehandle)里输入一份记录 \取某样东西的参考值 (reference)
注意 <FILE>不是用来指定档案的形态,亦非此把手的名字。它只是 将<>
这个运算子用在FILE这个把手上。在纯量的情境 (scalar context) 下,它自FILE 把手一次读入一行 (嗯,该说一笔记录,参看 $/),在序列情境 (list context)下,则一次将全部的内容读 入。当对档案使用开、关或其它<>
之外的动作、或甚至只是提到把 手时,切记不要使用<>
。下面的用法是正确的:eof(FH)
,seek(FH, 0,2)
以及 ``copying fromSTDIN to FILE''。
通常一个没有冠上形态符号的字 (bareword)是不需被纳入引号里的,但在大多数 的情况下或许该这麽做 (在use strict
下则是必须的)。但由一个简单的字(不 能是一个已定义的副函数之名称)所构成的索引值,和=>
左端的运算子,都会被视为已纳入引号了:
这些是和这些一样的 ------------ --------------- $foo{line} $foo{"line"} bar => stuff "bar" => stuff
一个区块末端的分号可有可无,一个序列的最後一个逗号亦同。良好的写作风格 (参看perlstyle)中建议除了在单行程式 (one-liners)的情况外都将他们加上去:
if ($whoops) { exit 1 } @nums = (1, 2, 3);
if ($whoops) { exit 1; } @lines = ( "There Beren came from mountains cold", "And lost he wandered under leaves", );
一种方法是将传回值当作序列来对待,然後用索引来指名其中的某个位置:
$dir = (getpwnam($user))[7];
另一种方法就是在等号左端用 undef 作元素:
($dev, $ino, undef, undef, $uid, $gid) = stat($file);
$^W
变数 (在perlvar中有说明)控制一个区块在执行期 (runtime)的警告讯息:{ local $^W = 0; #暂时关掉警告讯息 $a = $b + $c; #我知道这些变数可能未定义 }
注意,像所有的标点符号变数一样,目前不能对$^W
用 my,只能用 local()。
一个发展中的新use warnings
编译器指挥模组 (pragma) 提供了更精细的控制。好奇宝宝们应该翻翻 perl5-porters 邮件论坛的档案库以获得更详细的说明。
一种从 Perl呼叫编译好的C程式码的方法。阅读perlxstut是个多了解扩充(extensions)的好方法。
事实上它们是相同的。所有 Perl自C借过来的运算子都具备与原来在 C 中相同的优先顺序。问题出在那些C没有的运算子,特别是那些将其右方一律当成序列情境对待的函数,例如 print, chmod, exec等等。这类的函数被称作“序列运算子”(list operators),在 perlop的优先顺序表中就是这麽称呼。
一个常犯的错误像是:
unlink $file || die "snafu";
这会被解译器看成是:
unlink ($file || die "snafu");
要避免此问题,须加上括号或是用超低优先的or
运算子:
(unlink $file) || die "snafu"; unlink $file or die "snafu";
这些“英文的”运算子 (and
,or
,xor
,及 not
)是刻意设计成较一般序列运算子低的优先顺序,这就是为了解决前述的状况。
另一个拥有出人意料的优先顺序者为指数。它甚至高於负号,这使得 -2**2
变成负四而非正四。他同时也会“向右靠”(right-associate),意思是说 2**3**2
代表二的九次方,而不是八的平方。
一般来说,我们不 ``宣告''一个结构。用一个 (通常是匿名的) 杂凑阵列的参考值 (hash reference)即可。参看perlref 以及perldsc,里面有更多资料。以下是一个范例:
$person = {}; #新的不具名杂凑阵列 $person->{AGE} = 24; #把 AGE栏的值设成 24 $person->{NAME} = "Nat"; #将 NAME栏设成 "Nat"
如果你要的是更严谨的写法,看看perltoot 。
一个模组就是一个放在同名档案里的包裹(package)。例如,Hello::There模组会 放在Hello/There.pm。perlmod 里有详尽说明。Exporter 也会很有帮助。如果你正在写一个C 或是混合了C及 Perl 的模组,那麽你就该读perlxstut 。
下面是个方便的样板,你也许希望在撰写第一个模组时将他派上用场。记得要改名 字。
package Some::Module; #假设是 Some/Module.pm
use strict;
BEGIN { use Exporter (); use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
##设定版本以备版本检查之用;去掉 "#"号即可使用。 ## $VERSION = 1.00;
#如果有使用 RCS/CVS,那应该考虑将下一行保留, #但是小心两位数版本编号可能造成的影响。 $VERSION = do{my@r=q$Revision: 1.1 $=~/\d+/g;sprintf '%d.'.'%02d'x$#r,@r};
@ISA = qw(Exporter); @EXPORT = qw(&func1 &func2 &func3); %EXPORT_TAGS = ( ); #例如: TAG => [ qw!name1 name2! ],
#整个包裹要输出的全域变数(exported package globals)在此, #还有其他选择要输出的函数。 @EXPORT_OK = qw($Var1 %Hashit); } use vars @EXPORT_OK;
#没有输出的全域变数在此。 use vars qw( @more $stuff );
#起始包裹内的全域变数,首先是要输出的那几个。 $Var1 = ''; %Hashit = ();
#接下来是其他的 (还是能以 $Some::Module::stuff的方式撷取他们的值) $stuff = ''; @more = ();
#所有以档案为范围的变数名都 #必须在让後面的函数使用前先创造出来。
#以档案为范围的变数名在此。 my $priv_var = ''; my %secret_hash = ();
#下面是一个以档案为限的函数,当作一个闭包 #可以用 &$priv_func的方式呼叫它;但不能使用原型定义 my $priv_func = sub { #程式码放在这里 };
#不论是否要输出,都记得要将你的函数造出来。 #别忘了在 {}括号间放些有趣的内容。
sub func1 {} #没有定义原型 sub func2() {} #定原型为 void sub func3($$) {} #定原型为两个纯量值
#这个函数虽然未被输出,但是可以被呼叫 sub func4(\%) {} #定原型为一个杂凑阵列的参考值
END { } #模组清洁大队在此 (global destructor)
1; #模组必须传回真值
perltoot 里面有对於类别和物件的介绍,perlobj 和perlbot 也有。
参考Laundering and Detecting Tainted Data。以下是个范例 (里面没有用到任何系统呼叫,因为 kill()
没有将任何程序交给讯号):
sub is_tainted { return ! eval { join('',@_), kill 0; 1; }; }
然而,此方法会触发-w
参数的警告讯息。目前并无任何不会触发 -w
的方法可下侦测变数污染 -- 就把警告讯息当成是在提醒你,该把所有可能被污染的资料给 ``漂白'' (untaint)。
【译注:这里所提的 ``被污染'' (tainted),指的是当使用-T这个参数时,或当 perl程式做 setuid或 setgid模式执行时 (在UNIX 下), perl会自动将有安 全顾虑的变数列为受污染, 也就是 tainted。除非先做解除污染 (untaint)处理,否则 perl不会容许受污染的变数做出可能危害系统的举动。因此CGI程式应尽可能地使用-T这个参数以策安全。】
关於闭包的说明,请看perlref 。
闭包 (closure)是个精确但又很难解释的电脑名词。在 Perl 里面,闭包是以 匿名函数的形式来实现,具有持续参照位於该函数范围之外的文字式变数值的能力。 这些外部的文字变数会神奇地保留它们在闭包函数最初定义时的值 (深连结)。
如果一个程式语言容许函数递回另一个函数的话 (像 Perl 就是),闭包便具有意 义。要注意的是,有些语言虽提供匿名函数的功能,但却无法正确处理闭包; Python 这个语言便是一例。如果要想多了解闭包的话,建议你去找本功能性程式 设计的教科书来看。Scheme这个语言不仅支援闭包,更鼓励多加使用。
以下是个典型的产生函数的函数:
sub add_function_generator { return sub { shift + shift }; }
$add_sub = add_function_generator(); $sum = &$add_sub(4,5); # $sum现在是 9了
闭包用起来就像是个函数样板,其中保留了一些可以在稍後再填入的空格。 add_function_generator()
所递回的匿名函数在技术上来讲并不能算是一个闭包, 因为它没有用到任何位在这个函数范围之外的文字变数。
把上面这个例子和下面这个make_adder()
函数对照一下,下面这个函数所递回的匿名函数中使用了一个外部的文字变数。这种指名外部函数的作法需要由 Perl递回一个适当的闭包,因此那个文字变数在匿名函数产生之时的值便永久地被锁进闭 包里。
sub make_adder { my $addpiece = shift; return sub { shift + $addpiece }; }
$f1 = make_adder(20); $f2 = make_adder(555);
这样一来&$f1($n)
永远会是 20加上你传进去的值$n
,而&$f2($n)
将 永远会是 555加上你传进去的值$n
。$addpiece的值会在闭包中保留下来。
闭包在比较实际的场合中也常用得到,譬如当你想把一些程式码传入一个函数时:
my $line; timeout( 30, sub { $line = <STDIN> } );
如果要执行的程式码当初是以字串的形式传入的话,即'$line = <STDIN>'
,那麽timeout()
这个假想的函数在回到该函数被呼叫时所在的范围後便无法再撷取$list
这个文字变数的值了。
变数自杀指的是 (暂时或是永久)地失去一个变数的值。造成这个现象的原因是做范围界定的 my()
和local()和闭包或
foreach()
回圈变数及函数参数相互影响 所致。
从前【在旧版 perl的时代】大家写程式的时候很容易因为这样而不小心把变数值 给弄丢。但现在 perl提供了一些保护措施,因此犯这种错的机率要小多了。
my $f = "foo"; sub T { while ($i++ < 3) { my $f = $f; $f .= "bar"; print $f, "\n" } } T; print "Finally $f\n";
有叁个 ``bar''加进去的$f
变数应该是一个新的$f
(因为my $f
在每个回圈都应该创造一个新的区域变数)。然而,实际上并非如此。这个臭虫在未来的 perl 版本中将会被修正。
除了正规表现式这个特例外,你需要以传参考值的方式传资料给这些物件。参看 Pass by Reference,里面有针对此问题的讨论,以及perlref 里面有参考值的资讯。
func( \$some_scalar );
func( \$some_array ); func( [ 1 .. 10 ] );
func( \%some_hash ); func( { this => 10, that => 20 } );
func( \&some_func ); func( sub { $_[0] ** $_[1] } );
*FH
或 \*FH
(这叫 ``typeglobs'' --请参看perldata ),或是使用旧名 FileHandle的 IO::File模组以动态方式来产生档案把手亦可,这两个模组都附在标准 Perl 版本内。use Fcntl; use IO::File; my $fh = new IO::File $filename, O_WRONLY|O_APPEND; or die "Can't append to $filename: $!"; func($fh);
sub compare($$) { my ($val1, $regexp) = @_; my $retval = eval { $val =~ /$regexp/ }; die if $@; return $retval; }
$match = compare("old McDonald", q/d.*D/);
确定绝对不要用以下的写法:
return eval "\$val =~ /$regexp/"; #错误
不然某人可以靠双引号括起来的字串以及 eval 双重解译的本质而偷偷插入 shell指令来作坏事。例如:
$pattern_of_evil = 'danger ${ system("rm -rf * &") } danger';
eval "\$string =~ /$pattern_of_evil/";
想学非常非常聪明的方法的读者可以参考 O'Reilly 出的Mastering Regular Expressions这本书,作者是 Jeffrey Friedl。其中第 273页的Build_MatchMany_Function()
特别的有趣。在 perlfaq2中可以找到有关本书 的资料。
call_a_lot(10, $some_obj, "methname") sub call_a_lot { my ($count, $widget, $trick) = @_; for (my $i = 0; $i < $count; $i++) { $widget->$trick(); } }
不然你就用个闭包 (closure) 把物件和它的方法以及其参数都包在一起:
my $whatnot = sub { $some_obj->obfuscate(@args) }; func($whatnot); sub func { my $code = shift; &$code(); }
你也可以研究UNIVERSAL 类别中的can()
方法 (附於标准 Perl 版本中)。
就像与 Perl相关的其他事情一样,``条条大路通罗马'' (TMTOWTDI)。对其他语言来说叫做 ``静态变数'' (static variable)的东西,在 Perl里面可能是一个函数私有的变数(只有该函数自己看得到,且在不同的呼叫间保持定值),或是一个档案私有(file-private)变数(只有同一个档案中的函数才看得到)。
以下就是实作函数私有变数的程式:
BEGIN { my $counter = 42; sub prev_counter { return --$counter } sub next_counter { return $counter++ } }
prev_counter()
和next_counter()
将会共用一个於编译时起始的私有变数 $counter。
要宣告一个档案私有(file-private)变数,你仍然得使用my(),将它放在档案开
头处最外围。假设现在是在 Pax.pm 这个档案里:
package Pax; my $started = scalar(localtime(time()));
sub begun { return $started }
当用use Pax
或require Pax
载入此模组时,这个变数就会被起始。不过它不会被资源回收,像其他出了有效范围的变数那样,因为 begun()
函数要用到它,但是没有其他函数能撷取它。这个变数不能以 $Pax::started 的形式来撷取,因为它所存在的范围与此包裹无关。它存在的范围是这个档案。可想见地,一个档案里可以放好几个包裹,而所有的包裹都撷取同一个私有变数,但从另一个档案中,即使是属於同一个包裹(package),也不能取得它的值。
local($x)
将全域变数$x
的原值存起来,并在此函数执行期间赋予一个新 值,此值可以从此函数所呼叫的其他函数里看见。这整个步骤是在执行期间完成的,所以才叫做动态范围选取 (dynamic scoping)。local()影响的是全域变数,或者称作包裹变数或动态变数。
my($x)
会创造一个只能在目前这个函数里看得见的新变数。这个步骤是在编译 期完成(compile-time),所以称作文字式或是静态范围选取。my()总是作用在私 有变数,也称作文字式变数或(不当地)称作静态(范围选取)变数。
例如:
sub visible { print "var has value $var\n"; }
sub dynamic { local $var = 'local'; #授予 $var这个全域变数 visible(); #一个暂时的新值 }
sub lexical { my $var = 'private'; #新的私有变数,$var visible(); # (无法从此函数外看到) }
$var = 'global';
visible(); #会印出 global dynamic(); #会印出 local lexical(); #会印出 global
你可以发现在整个过程中 ``private''这个值都印不出来。那是因为$var
的值只存在於lexical() 函数的区块里面,对它所呼叫的函数来说是看不到的。
总结来说,local()不会产生你想像中的私有、区域变数。它只是将一个暂时的值 授予一个全域变数。如果你要的是私有的变数,那麽my()
才是你要找的。
参看perlsub ,里面有更详尽的解说。
你可以透过符号式参考值 (symbolic references),把use strict "refs"
设定取掉。然後使用${'var'}
,而非 $var。
local $var = "global"; my $var = "lexical";
print "lexical is $var\n";
no strict 'refs'; print "global is ${'var'}\n";
如果你知道你所在的是哪一个包裹(package)的话,你可以直接指名,就像写 $Some_Pack::var这样。注意 $::var这个写法并非表示目前此包裹 (package) 内的动态变数 $var,而是指在main
包裹(package) 里的那个,就等价於 $main::var。直接指定包裹(package)的名称虽然需要你把名字敲进程式码 中,但是它执行起来比较快,也避免和use strict "refs"
起冲突。
在深连结中,匿名函数中所用到的文字式变数值是以该函数产生时所在的范围为准。在浅连结中,这些变数值是以函数被呼叫时所在的范围为准,如果在这个范围中恰巧有同名的变数,便使用这些当地变数的值。Perl总是使用文字式变数(就是以 my()
创造的)式的深连结。然而,动态变数(也称作全域(global),区域(local),或包裹(package)变数)在功效上是浅连结。就把这当作是少用它们的另一个理由好 了。请参考闭包 (closure)是啥? 一节。
local()
会把=
号右边以序列情境来对待。而 <FH>这个阅读的 动作,就像 Perl里许多的函数以及运算子一样,会自动分辨出自己被呼叫时所在的情境并且采取适当的作法。一般来说,scalar()函数可以帮点忙。这个函数实际上对资料本身不会有任何作用(与一般所认为的相反),但是会告诉它所作用的函数要以对待纯量值的方法来运算。如果那个函数没有预先定义好碰到纯量情境的行为,那麽它当然也帮不了你(例如 sort()
函数)。
然而,在以上这个例子 (local...)中,只要省略括号便可强制使用纯量情境:
local($foo) = <FILE>; #错误 local($foo) = scalar(<FILE>); #可以 local $foo = <FILE>; #正确
其实在这个例子中,或许你该改用文字式变数 (lexical variables),不过会碰到 的问题跟上面一样:
my($foo) = <FILE>; #错误 my $foo = <FILE>; #正确
为什麽要这麽做? :-)
如果你要覆盖掉某个内建函数,例如说open(),那你得将其定义从另一个模组载
入。参考Overriding Builtin Functions。在Class/Template里面也有个范例。
如果你要覆盖掉一个 Perl运算子,像是+
或**
,那你该使用use overload
这个编译器指挥模组(pragma),其文件在 overload 。
如果你要覆盖父类别 (parent class)里的方法呼叫 (method calls),请看Overridden Methods 。
当你用&foo
的方式呼叫一个函数时,你等於让这个函数撷取你目前 @_
里面的值,同时也跳过原型定义 (prototypes)不用。这表式此函数抓到的是你当时的 @_, 而非一个空的 @_!虽然严格讲起来它也不能算是个 bug (但是在perlsub里面是这麽说的)但在大部份情况下,这也算不上是个特别功能。
当你用&foo()
的方式呼叫你的函数时,你会得到一个新的 @_,但是原型定义 仍然会被避开不用。
在一般情况下,你该用foo()
的方式去呼叫函数。只有在编译器已事先知道这 个函数的定义时,括号才能省略,譬如当这个函数所在的模组或包裹被 use
(但如果是被require
则不行)时,或是透过先前提及或 use subs
宣告等 方法,让编译器先接触到这个函数的定义。用这种呼叫方式,即使是当括号省掉时, 你都会得到一个乾净的 @_,不会有任何不该出现的旧值残留在上面。
这个问题在perlsyn 文件里有更详尽的解释。简单来说,因为 Perl本身已提供了多种不同的条件测试方法可供使用 (数值比较、字串比较、 glob比较、正规表示式 对应、覆盖比较,及其它),所以并没有正式的 case叙述语法。虽然自 perl1起这就一直是许多人期盼的一个项目,但因 Larry无法决定怎样才是呈现这功能的最好方法,因此还是将它略掉。
下面这个简单的 switch范例以模式对应为基础。我们将要做的是对储存在 $whatchamacallit
里面的参考值 (reference)的类型进行多重条件的判断。【译注:$whatchamacallit
函意为$what_you_might_call_it
】
SWITCH: for (ref $whatchamacallit) {
/^$/ && die '不是个参考值';
/SCALAR/ && do { print_scalar($$ref); last SWITCH; };
/ARRAY/ && do { print_array(@$ref); last SWITCH; };
/HASH/ && do { print_hash(%$ref); last SWITCH; };
/CODE/ && do { warn '无法印出函数的 ref'; last SWITCH; };
# DEFAULT
warn '跳过使用者自定的类型';
}
在Autoloading 和AUTOLOAD: Proxy Methods里 提到的AUTOLOAD 方法让你能捕捉对於未定义函数与方法的呼叫。
如果是要处理一些在-w
之下触发警告讯息的未定义变数,你可以使用一个处理元 (handler)来捕捉__WARN__
这个虚拟信号 (pseudo-signal),范例如下:
$SIG{__WARN__} = sub {
for ( $_[0] ) {
/Use of uninitialized value/ && do { #将警讯提升为致命行动 die $_; };
#其它要捕捉的状况可以写在此。
warn $_; }
};
一些可能的原因:你用的继承给搞混了、你拼错了该方法的名字,或是物件的类别 错误。这些事在perltoot里都有更详尽的说明。同时你也可以用 print ref($object)
来找出$object
这个物件是被归到哪个类别底下。
另一个可能的原因是你在 Perl还不知道这个包裹 (package)存在之前便将某个 类别名称在间接式物件语法中使用 (例如find Guru "Samy"
)。最好是在开始使用你的包裹前,先确定都已经先把它们定义好了,如果你用的是 use
而非require
的话,这件事便会自动处理好。不然的话,确定你使用箭头式语法 (例如,Guru-find(``Samy'')>)。在
perlobj 里面对於物件的记号有详尽解释。
如果只是一个随意的程式的话,你可以用下面的方法找出目前正被编译的包裹为何:
my $packname = ref bless [];
但如果是一个方法的话,而且印出的错误讯息中要包含呼叫此方法的物件 (不见得 就是把这个方法编译进去的那个物件)则:
sub amethod { my $self = shift; my $class = ref($self) || $self; warn "我是被 $class这个物件所召唤"; }
用内嵌POD格式的方法把程式码变注解:
#这是程式
=for nobody 这段就变成了注解
#程式继续下去
=begin comment text
接下来此处所有
的文字都会被 所有人忽略
=end comment text
=cut