perlsyn

名称

perlsyn - Perl 语法


描述

一个 Perl 脚本包含一系列的声明和语句. 在 Perl 里, 唯一需要声明的东西是 报告格式和子过程. 下面的内容会有更多关于声明的内容. 所有未经初始化的用户自定义对象的初值是空 或 0, 直到被显式的定义为止(如果需要, 也可以对使用未定义变量做出警告). sedawk 脚本会对每一行输入的数据重复执行, 而Perl 的语句只会被执行一次. 所以如果必须显式的指定循环以便处理输入文件的各行, 当然这样才可以控制处理哪些文件和哪些行需要处理. (这里有点言不尽实, 有隐式的循环, 用选项 -n-p . 但这不象 sedawk 里是做为默认的设置.)


声明

在大部分地方, Perl 是一种自由格式的语言(唯一的例外是格式声明, 这当然马虎不得). 注释部分用 ``#'' 开头, 直到行末. 如果用 C 风格的 /* */ 来做注释, 会被解释成分离操作或模式匹配, 依上下文而定, C++ 的 // 注释会被当作 一个空的正则表达式, 这些注释都是不对的.

声明可以出现在任何语句可以出现的地方, 但执行的时候并不起作用 -- 声明只在 编译的时候起作用. 一般所有的声明都放在脚本的开头或结尾. 不过, 如果要使用 用my()建立的有语义范围的私有变量, 就要保证 格式或子过程要出现在想访问它们的块范围内.

Declaring a subroutine allows a subroutine name to be used as if it were a list operator from that point forward in the program. You can declare a subroutine without defining it by saying just 一旦子过程被声明, 就可以把它的名字看作一个列表操作符. 可以只是声明一个子过程 而不进行定义

sub myname; $me = myname $0 or die "can't get myname";

注意这里子过程是列表操作符, 而不是一元操作符, 所以不能用 || 要用 or.

子过程的声明也可以用语句 require 装载. 或者用语句 use 把子过程装载并调进名字空间. 详细信息参看 perlmod .

A statement sequence may contain declarations of lexically-scoped variables, but apart from declaring a variable name, the declaration acts like an ordinary statement, and is elaborated within the sequence of statements as if it were an ordinary statement. That means it actually has both compile-time and run-time effects. 语句可以包含有语义范围的变量声明, 但除了声明变量的名字, 声明本身也是一个普通语句. 表示它实际上在编译时刻和运行时刻都起作用.


简单的语句

简单的语句是表达式的求值. 每个简单的语句都要用分号结尾, 除非它是一个块里的最后 一条语句. 要注意有些操作符如 eval {}do {} that look 看起来象是复合语句, 但其实不是, 如果它们是一个语句的最后一个元素, 就要显式的终止.

任何简单语句可以尾随一个修饰符, 在结尾的分号前(或块结尾前). 可能的修饰符有:

if EXPR unless EXPR while EXPR until EXPR

修饰符 ifunless 在英语里有特定的语意. 修饰符 whileuntil 也有当...时循环的语意(检查循环条件), 除了附加到do块的时候, do块中的语句是在循环条件被检查前执行.

do { $line = <stdin>; ... } until $line eq &quot;.\n&quot;;

参看 do . 要注意后面会说到的循环控制语句不是这样组成的, 因为修饰符不能对应循环标号.


复合语句

在 Perl 里, 确定了一个范围的一系列语句称为一个块. 有时候块的边界是包含它的文件 (用 required 的时候, 或者整个程序就是一个文件), 有时候块的边界是字符串的范围 (用 eval 的时候).

但一般来说, 块是用花括号分隔的, 称为语法块.

下面的复合语句可以用做流程控制:

if (EXPR) BLOCK if (EXPR) BLOCK else BLOCK if (EXPR) BLOCK elsif (EXPR) BLOCK ... else BLOCK LABEL while (EXPR) BLOCK LABEL while (EXPR) BLOCK continue BLOCK LABEL for (EXPR; EXPR; EXPR) BLOCK LABEL foreach VAR (LIST) BLOCK LABEL BLOCK continue BLOCK

要注意, 和 C 或 Pascal 不一样, 这里用的术语是块, 而不是语句. 这意味着花括号是 必须出现的. 如果想写没有花括号的条件执行语句, 有几种办法. 下面的语句做的是同样 的事情:

if (!open(FOO)) { die &quot;Can't open $FOO: $!&quot;; } die &quot;Can't open $FOO: $!&quot; unless open(FOO); open(FOO) or die &quot;Can't open $FOO: $!&quot;; # FOO or bust! open(FOO) ? 'hi mom' : die &quot;Can't open $FOO: $!&quot;; # a bit exotic, that last one

if 语句是直接的. 由于块总是被花括号包围, 所以不会出现 ifelse 混乱的情况. 如果用 unless 代替 if, 条件测试的含义就是相反的.

只要条件表达式的值为真(不是空字符串, 数值 0 或字符串 ``0''), while 语句就不断执行其中的块. 标号是可选的, 标号是标识符后跟一个冒号. 标号为循环控制语句 next , last , 和 redo 指明代表的循环. 如果没有标号, 这些循环控制语句作用在最近一层的循环. 在运行时刻, 查找标号可能会引起 对调用栈的动态回查. 这是不建议的操作, 在使用 -w 选项时会产生警告信息.

continue 块在循环条件被再次检查前执行, 就象 C 里的 for 循环的第3部分一样. 所以 可以用它给循环变量进行增量操作, 甚至是当循环从语句 next (类似于 C 的 continue 语句)再次进入的时候.


循环控制

next 命令和 C 里的 continue 语句一样, 使程序重新进入循环体.

LINE: while (<stdin>) { next LINE if /^#/; # discard comments ... }

last 命令和 C 里的 break 语句一样(用于循环体内时); 使程序立刻跳出循环. 任何 continue 块是不会被执行的:

LINE: while (<stdin>) { last LINE if /^$/; # exit when done with header ... }

redo 重新开始进入循环块, 但不重新计算循环条件. continue 块也不会执行. 这个命令用于需要忽略当前输入的情况.

例如, 当处理一个文件 /etc/termcap 时. 如果输入行以反斜杠结束, 表示 继续接受输入, 就可以跳过后面的处理去读入下一行.

while (<>) { chomp; if (s/\\$//) { $_ .= <>; redo unless eof(); } # now process $_ }

更加明白的写法是:

LINE: while ($line = <argv>) { chomp($line); if ($line =~ s/\\$//) { $line .= <argv>; redo LINE unless eof(); # not eof(ARGV)! } # now process $line }

另一个简化的去掉 Pascal 程序里注释的程序(警告: 默认 { 或 } 不会出现在字符串里)

LINE: while (<stdin>) { while (s|({.*}.*){.*}|$1 |) {} s|{.*}| |; if (s|{.*| |) { $front = $_; while (<stdin>) { if (/}/) { # end of comment? s|^|$front{|; redo LINE; } } } print; }

要注意如果上面的程序里有 continue 块, 即使对是被忽略的行, 它也会执行.

If the word while is replaced by the word until, the sense of the test is reversed, but the conditional is still tested before the first iteration. 如果用 until 代替上面的 while, 条件测试的含义是相反的, 但条件总是在第一次执行循环体前被测试.

if 语句和 while 语句, 可以把 ``(EXPR)'' 换成一个块, 条件的值是块里最后一条执行语句的布尔值. 但不建议这么做, 请把 ``if BLOCK'' 写成 ``if (do BLOCK)''.


For 循环

Perl 的 C 风格 for 循环和相应的 while 循环是一样的. 即

for ($i = 1; $i <10; $i++) { ... }

和下面的循环含义一样:

$i = 1; while ($i <10) { ... } continue { $i++; }

除了普通的数组循环, for 还有很多有趣的用法. 下面代码避免了 在交互式的文件描述符上测试文件结束符引起的挂起问题.

$on_a_tty = -t STDIN &amp;&amp; -t STDOUT; sub prompt { print &quot;yes? &quot; if $on_a_tty } for ( prompt(); <stdin>; prompt() ) { # do something }


Foreach 循环

foreach 循环依次把一个列表的元素赋给变量 VAR. 这个变量对循环隐含 为局部的, 当循环结束的时候恢复原来的值. 如果变量是用 my 声明的, 就不会用全局变量而使用这个变量, 但变量还是局部于循环. 如果在循环体内有 子过程或格式的声明, 可能会引起问题.

foreach 关键字是 for 关键字的同意词, 用 foreach 看起来更明白, 用 for 看起来更简洁. 如果省略 VAR, $_ 被用来接受每个值. 如果 LIST 是个数组(不同于返回列表值的表达式), 在循环里修改 VAR 的值就可以修改数组元素的值. 因为 foreach 的索引变量是循环列表 里每个元素的隐含别名.

例子:

for (@ary) { s/foo/bar/ } foreach $elem (@elements) { $elem *= 2; } for $count (10,9,8,7,6,5,4,3,2,1,'BOOM') { print $count, &quot;\n&quot;; sleep(1); } for (1..15) { print &quot;Merry Christmas\n&quot;; } foreach $item (split(/:[\\\n:]*/, $ENV{TERMCAP})) { print &quot;Item: $item\n&quot;; }

用 C 的风格表达的算法:

for ($i = 0; $i <@ary1; $i++) { for ($j="0;" $j < @ary2; $j++) { if ($ary1[$i]> $ary2[$j]) { last; # can't go to outer :-( } $ary1[$i] += $ary2[$j]; } # this is where that last takes me }

用 Perl 的风格要舒服得多:

OUTER: foreach $wid (@ary1) { INNER: foreach $jet (@ary2) { next OUTER if $wid &gt; $jet; $wid += $jet; } }

这样的代码更清楚, 更安全, 执行起来更快.


基本的 BLOCKs 和 Switch 语句

一个块(带或不带标号)在语意上等同于一个执行一次的循环. 因此可以用任何循环控制 语句来离开或重新进入块. continue 块是可选的.

块的构造很适合用于 case 结构.

SWITCH: { if (/^abc/) { $abc = 1; last SWITCH; } if (/^def/) { $def = 1; last SWITCH; } if (/^xyz/) { $xyz = 1; last SWITCH; } $nothing = 1; }

在 Perl 里并没有正式的 switch 语句, 因为有很多方法可以构造. 除了上面的例子, 还可以这样写

SWITCH: { $abc = 1, last SWITCH if /^abc/; $def = 1, last SWITCH if /^def/; $xyz = 1, last SWITCH if /^xyz/; $nothing = 1; }

(如果明白在表达式里也可以使用循环控制语句, 就不难看懂上面的代码)

又或者

SWITCH: { /^abc/ &amp;&amp; do { $abc = 1; last SWITCH; }; /^def/ &amp;&amp; do { $def = 1; last SWITCH; }; /^xyz/ &amp;&amp; do { $xyz = 1; last SWITCH; }; $nothing = 1; }

或者写得更象一个``正规''的 switch 语句:

SWITCH: { /^abc/ &amp;&amp; do { $abc = 1; last SWITCH; }; /^def/ &amp;&amp; do { $def = 1; last SWITCH; }; /^xyz/ &amp;&amp; do { $xyz = 1; last SWITCH; }; $nothing = 1; }

或者是

SWITCH: { /^abc/ and $abc = 1, last SWITCH; /^def/ and $def = 1, last SWITCH; /^xyz/ and $xyz = 1, last SWITCH; $nothing = 1; }

甚至这样可怕

if (/^abc/) { $abc = 1 } elsif (/^def/) { $def = 1 } elsif (/^xyz/) { $xyz = 1 } else { $nothing = 1 }

更常用的方法是用 foreach 设置 $_ 的值, 然后匹配起来就很方便:

SWITCH: for ($where) { /In Card Names/ &amp;&amp; do { push @flags, '-e'; last; }; /Anywhere/ &amp;&amp; do { push @flags, '-h'; last; }; /In Rulings/ &amp;&amp; do { last; }; die &quot;unknown value for form variable where: `$where'&quot;; }

另一种更有趣的写 switch 语句的办法是让 do 返回适当的值:

$amode = do { if ($flag &amp; O_RDONLY) { &quot;r&quot; } elsif ($flag &amp; O_WRONLY) { ($flag &amp; O_APPEND) ? &quot;w&quot; : &quot;a&quot; } elsif ($flag &amp; O_RDWR) { if ($flag &amp; O_CREAT) { &quot;w+&quot; } else { ($flag &amp; O_APPEND) ? &quot;r+&quot; : &quot;a+&quot; } } };


Goto

Perl 有 goto 语句. 循环的标号实际上不是 goto 的有效目标; 只是循环的名字而已. goto 的目标有三种: goto-LABEL, goto-EXPR, 和 goto-&NAME.

goto-LABEL 方式是找到以 LABEL 标注的语句, 然后从该点执行. 不该用这种方法 进入需要初始化的地方, 比如子过程或 foreach 循环. goto-LABEL 可以在动态范围内 随意跳转, 包括跳出子过程, 但最好还是用 last 和 die 来做. Perl 的作者从来不认为 有必要使用这种方式的 goto.

goto-EXPR 要求一个标号名字, 这个名字的范围是动态确定的. 这种方式允许计算跳转 的位置, 但如果想代码容易维护, 不要这么做.

goto (&quot;FOO&quot;, &quot;BAR&quot;, &quot;GLARCH&quot;)[$i];

goto-&NAME 方式很特别, 它按名字调用一个子过程, 并用这个子过程代替正在执行的 子过程. AUTOLOAD() 子过程用这个方法调用另外一个子过程, 并使后者好象是 一开始就被调用的(除了本过程对 @_ 的修改会被传递到新的子过程). 在 goto 以后, 连 caller() 也不知道这个过程是否先被调用的.

在大部分场合下, 最好是用结构化的流程控制结构如 next , last , 或 redo 代替 goto . 对特定的程序, 捕获并抛出 eval{}die() 对来处理异常情况也是审慎的做法.


PODs: 内嵌的文档

Perl 有把文档和代码混合在一起的机制. 如果编译器期待一个语句的遇到了以 一个等号后跟一个词开头的行, 象

=head1 Here There Be Pods!

那么从这行开始直到以 =cut 开始的行之间的所有文本都被忽略. 插入的文本的格式参看 perlpod .

这样就可以把代码和文档混合存放了.

=item snazzle($) The snazzle() function will behave in the most spectacular form that you can possibly imagine, not even excepting cybernetic pyrotechnics. =cut back to the compiler, nuff of this pod stuff! sub snazzle($) { my $thingie = shift; ......... }

要注意 pod 翻译器只会检查带 pod 标记的段落, 而编译器在一段的中间也能 剔除 pod 文字. 所以编译器和翻译器都不会理睬下面的 secret 内容.

$a=3; =secret stuff warn &quot;Neither POD nor CODE!?&quot; =cut back print &quot;got $a\n&quot;;

不要尽信 warn() 的输出. 并非所有的 pod 翻译器都工作得很严格, 编译器可能更挑剔一些.