内容

名称

perlsyn - Perl 语法

说明

Perl 程序由一系列从上到下运行的声明和语句组成。循环、子例程和其他控制结构允许你在代码中跳转。

Perl 是一种自由格式语言:你可以根据需要对其进行格式化和缩进。空白主要用于分隔标记,这与 Python(空白是语法的重要组成部分)或 Fortran(空白无关紧要)等语言不同。

Perl 的许多语法元素都是可选的。你通常可以省略括号和变量声明等显式元素,Perl 会弄清楚你的意思,而不是要求你在每个函数调用周围加上括号并声明每个变量。这被称为按我的意思做,缩写为DWIM。它允许程序员偷懒并以他们习惯的风格编写代码。

Perl 借用语法和概念,这些语法和概念来自很多语言:awk、sed、C、Bourne Shell、Smalltalk、Lisp 甚至英语。其他语言也借用了 Perl 的语法,特别是它的正则表达式扩展。因此,如果您用其他语言进行过编程,您将在 Perl 中看到熟悉的片段。它们通常以相同的方式工作,但请参阅 perltrap 了解它们的不同之处。

声明

在 Perl 中,您只需要声明报告格式和子例程(有时甚至不需要子例程)。标量变量保存未定义的值 (undef),直到为其分配定义的值,即除 undef 之外的任何值。当用作数字时,undef 被视为 0;当用作字符串时,它被视为空字符串 "";当用作未分配的引用时,它被视为错误。如果您启用警告,每当您将 undef 视为字符串或数字时,您都会收到未初始化值的通知。好吧,通常情况下是这样。布尔上下文,例如

if ($a) {}

不包含警告(因为它们关注的是真值,而不是定义)。诸如 ++--+=-=.= 等对未定义变量(例如

undef $a;
$a++;

)进行操作的运算符也始终不包含此类警告。

声明可以放在任何可以放置语句的位置,但对语句主序列的执行没有影响:所有声明在编译时都会生效。所有声明通常放在脚本的开头或结尾。但是,如果您使用 my()state()our() 创建词法作用域的私有变量,您必须确保您的格式或子例程定义与 my 位于同一块作用域中,如果您希望能够访问这些私有变量。

声明一个子例程允许将子例程名称用作列表运算符,从程序中的该点开始。您可以通过声明 sub name 来声明一个子例程,而不定义它,如下所示

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

像这样的一个空声明将函数声明为一个列表操作符,而不是一个一元操作符,所以你必须小心使用括号(或使用 or 而不是 ||)。|| 操作符绑定太紧密,不能在列表操作符之后使用;它将成为最后一个元素的一部分。你始终可以在列表操作符参数周围使用括号,将列表操作符转换回更像函数调用的东西。或者,你可以使用原型 ($) 将子例程转换为一元操作符

sub myname ($);
$me = myname $0             || die "can't get myname";

现在可以按预期解析,但你仍然应该养成在这种情况下使用括号的习惯。有关原型的更多信息,请参阅 perlsub

子例程声明也可以使用 require 语句加载,或者使用 use 语句加载并导入到你的命名空间中。有关此内容的详细信息,请参阅 perlmod

语句序列可能包含词法作用域变量的声明,但除了声明变量名之外,声明的行为就像一个普通语句,并且在语句序列中进行详细说明,就好像它是一个普通语句一样。这意味着它实际上既有编译时效果,也有运行时效果。

注释

"#" 字符到行尾的文本为注释,并且会被忽略。例外包括字符串或正则表达式中的 "#"

简单语句

唯一一种简单语句是为其副作用求值的表达式。每个简单语句都必须以分号结尾,除非它是块中的最后一个语句,在这种情况下分号是可选的。但如果块占用了多行,无论如何都要加上分号,因为你最终可能会添加另一行。请注意,有一些操作符(如 eval {}sub {}do {}看起来像复合语句,但实际上不是——它们只是表达式中的 TERM——因此在用作语句中的最后一个项目时需要明确终止。

语句修饰符

任何简单语句在终止分号(或块结尾)之前都可能选择性地跟随一个单个修饰符。可能的修饰符是

if EXPR
unless EXPR
while EXPR
until EXPR
for LIST
foreach LIST
when EXPR

修饰符后面的 EXPR 称为“条件”。它的真或假决定了修饰符的行为。

if 仅当条件为真时执行语句一次。unless 相反,仅当条件不为真(即条件为假)时执行语句。有关真和假的定义,请参见 perldata 中的“标量值”

print "Basset hounds got long ears" if length $ear >= 10;
go_outside() and play() unless $is_raining;

for(each) 修饰符是一个迭代器:它对 LIST 中的每个项目执行语句一次(其中 $_ 依次别名为每个项目)。此形式中没有语法来指定 C 风格的 for 循环或词法作用域迭代变量。

print "Hello $_!\n" for qw(world Dolly nurse);

while 在条件为真时重复语句。后缀 while 对某些类型的条件具有与前缀 while 相同的神奇处理。until 相反,在条件为真(或条件为假)时重复语句

# Both of these count from 0 to 10.
print $i++ while $i <= 10;
print $j++ until $j >  10;

whileuntil 修饰符具有通常的“while 循环”语义(首先评估条件),但当应用于 do-BLOCK(或 Perl4 do-SUBROUTINE 语句)时除外,在这种情况下,在评估条件之前执行块一次。

这样可以编写如下循环

do {
    $line = <STDIN>;
    ...
} until !defined($line) || $line eq ".\n"

参见 perlfunc 中的“do”。还要注意,稍后描述的循环控制语句将在此构造中工作,因为修饰符不采用循环标签。抱歉。您始终可以在其内部(对于 next/redo)或其周围(对于 last)放置另一个块来执行此类操作。

对于 nextredo,只需将大括号加倍

do {{
    next if $x == $y;
    # do something here
}} until $x++ > $z;

对于 last,您必须更详细,并在其周围放置大括号

{
    do {
        last if $x == $y**2;
        # do something here
    } while $x++ <= $z;
}

如果您需要 nextlast,则必须同时执行这两项操作,并且还要使用循环标签

LOOP: {
    do {{
        next if $x == $y;
        last LOOP if $x == $y**2;
        # do something here
    }} until $x++ > $z;
}

注意:使用语句修饰符条件或循环构造(例如,my $x if ...)修改的 mystateour 的行为是未定义的my 变量的值可能是 undef、任何先前分配的值或其他任何值。不要依赖它。将来版本的 perl 可能会执行与您尝试的 perl 版本不同的操作。此处有龙。

when 修饰符是一个实验性功能,首次出现在 Perl 5.14 中。要使用它,您应该包含一个 use v5.14 声明。(从技术上讲,它只需要 switch 功能,但该方面在 5.14 之前不可用。)仅在 foreach 循环或 given 块内有效,仅当智能匹配 $_ ~~ EXPR 为真时才执行语句。如果语句执行,则在 foreach 中会后跟一个 next,在 given 中会后跟一个 break

在当前实现下,foreach 循环可以在 when 修饰符的动态作用域内的任何位置,但必须在 given 块的词法作用域内。此限制可能会在未来版本中放宽。请参见下面的 “Switch 语句”

复合语句

在 Perl 中,定义作用域的语句序列称为块。有时一个块由包含它的文件(在必需文件或整个程序的情况下)进行分隔,有时一个块由字符串的范围(在 eval 的情况下)进行分隔。

但通常,一个块由花括号(也称为大括号)进行分隔。我们将此语法结构称为 BLOCK。由于括号也用于哈希引用构造器表达式语法(请参阅 perlref),因此您可能偶尔需要通过在开括号后立即放置 ; 来消除歧义,以便 Perl 意识到大括号是块的开头。您更频繁地需要通过在开括号前立即放置 + 来消除歧义,以强制将其解释为哈希引用构造器表达式。自由使用这些消除歧义的机制被认为是一种良好的风格,而不仅仅是在 Perl 猜测错误的情况下。

以下复合语句可用于控制流

if (EXPR) BLOCK
if (EXPR) BLOCK else BLOCK
if (EXPR) BLOCK elsif (EXPR) BLOCK ...
if (EXPR) BLOCK elsif (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

given (EXPR) BLOCK

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 for VAR (LIST) BLOCK
LABEL for VAR (LIST) BLOCK continue BLOCK

LABEL foreach (EXPR; EXPR; EXPR) BLOCK
LABEL foreach VAR (LIST) BLOCK
LABEL foreach VAR (LIST) BLOCK continue BLOCK

LABEL BLOCK
LABEL BLOCK continue BLOCK

PHASE BLOCK

从 Perl 5.36 开始,您可以通过在括号内指定词法列表来一次迭代多个值

no warnings "experimental::for_list";
LABEL for my (VAR, VAR) (LIST) BLOCK
LABEL for my (VAR, VAR) (LIST) BLOCK continue BLOCK
LABEL foreach my (VAR, VAR) (LIST) BLOCK
LABEL foreach my (VAR, VAR) (LIST) BLOCK continue BLOCK

如果通过实验性 try 功能启用,还可以使用以下功能

try BLOCK catch (VAR) BLOCK
try BLOCK catch (VAR) BLOCK finally BLOCK

实验性的 given 语句不会自动启用;请参阅下面的 "Switch 语句" 以了解如何执行此操作以及相关的注意事项。

与 C 和 Pascal 不同,在 Perl 中,这些都是根据 BLOCK 定义的,而不是语句。这意味着花括号是必需的——不允许悬空语句。如果您想在没有花括号的情况下编写条件语句,还有其他几种方法可以做到这一点。以下所有操作都执行相同操作

if (!open(FOO)) { die "Can't open $FOO: $!" }
die "Can't open $FOO: $!" unless open(FOO);
open(FOO)  || die "Can't open $FOO: $!";
open(FOO) ? () : die "Can't open $FOO: $!";
    # a bit exotic, that last one

if 语句很简单。由于 BLOCK 总是由花括号限定,因此 else 与哪个 if 匹配永远不会有任何歧义。如果您使用 unless 代替 if,则测试的意义将颠倒。与 if 一样,unless 后面可以跟 elseunless 甚至可以后跟一个或多个 elsif 语句,尽管在使用该特定语言结构之前您可能需要三思而后行,因为阅读您代码的每个人都必须三思而后行才能理解正在发生的事情。

只要表达式为真,while 语句就会执行该块。只要表达式为假,until 语句就会执行该块。LABEL 是可选的,如果存在,则由一个标识符后跟一个冒号组成。LABEL 为循环控制语句 nextlastredo 标识循环。如果省略 LABEL,则循环控制语句将引用最内层的封闭循环。这可能包括在运行时动态搜索您的调用堆栈以查找 LABEL。如果您使用 use warnings pragma 或 -w 标志,这种绝望的行为会触发警告。

如果 while 语句的条件表达式基于任何一组迭代表达式类型,那么它会得到一些神奇的处理。受影响的迭代表达式类型有 readline<FILEHANDLE> 输入运算符、readdirglob<PATTERN> 全局运算符和 each。如果条件表达式是这些表达式类型之一,那么迭代运算符产生的值将隐式赋值给 $_。如果条件表达式是这些表达式类型之一或其中一个表达式显式赋值给标量,那么条件实际上测试表达式值的已定义性,而不是其常规真值。

如果存在 continue BLOCK,它总是在条件即将再次求值之前执行。因此,即使通过 next 语句继续循环,也可以使用它来递增循环变量。

当一个块之前是编译阶段关键字,例如 BEGINENDINITCHECKUNITCHECK,那么该块将仅在执行的相应阶段运行。有关更多详细信息,请参阅 perlmod

扩展模块还可以连接到 Perl 解析器以定义新类型的复合语句。这些语句由扩展识别的关键字引入,并且关键字后面的语法完全由扩展定义。如果您是实现者,请参阅 "perlapi 中的 PL_keyword_plugin" 以了解机制。如果您正在使用此类模块,请参阅模块文档以了解其定义的语法的详细信息。

循环控制

next 命令启动循环的下一个迭代

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

last 命令立即退出当前循环。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 $_
}

这是 Perl 简写,表示更明确的书面版本

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

请注意,如果在上述代码中存在 continue 块,则它将仅在正则表达式丢弃的行上执行(因为 redo 会跳过 continue 块)。continue 块通常用于重置行计数器或 m?pat? 一次性匹配

# inspired by :1,$g/fred/s//WILMA/
while (<>) {
    m?(fred)?    && s//WILMA $1 WILMA/;
    m?(barney)?  && s//BETTY $1 BETTY/;
    m?(homer)?   && s//MARGE $1 MARGE/;
} continue {
    print "$ARGV $.: $_";
    close ARGV  if eof;             # reset $.
    reset       if eof;             # reset ?pat?
}

如果用单词 until 替换单词 while,测试的意义将被颠倒,但条件仍然在第一次迭代之前进行测试。

循环控制语句不能在 ifunless 中使用,因为它们不是循环。不过,你可以将大括号加倍以使其成为循环。

if (/pattern/) {{
    last if /fred/;
    next if /barney/; # same effect as "last",
                      # but doesn't document as well
    # do something here
}}

这是因为块本身充当执行一次的循环,请参见 "Basic BLOCKs"

Perl 4 中可用的形式 while/if BLOCK BLOCK 不再可用。用 if (do BLOCK) 替换 if BLOCK 的任何出现。

For 循环

Perl 的 C 样式 for 循环与相应的 while 循环类似;这意味着

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

与这个相同

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

有一个细微的区别:如果变量在 for 的初始化部分用 my 声明,则这些变量的词法范围恰好是 for 循环(循环体和控制部分)。为了说明

my $i = 'samba';
for (my $i = 1; $i <= 4; $i++) {
    print "$i\n";
}
print "$i\n";

执行时,给出

1
2
3
4
samba

作为特例,如果 for 循环(或相应的 while 循环)中的测试为空,则将其视为真。也就是说,

for (;;) {
    ...
}

while () {
    ...
}

都被视为无限循环。

除了正常的数组索引循环之外,for 还可以用于许多其他有趣的应用程序。这里有一个避免了在交互式文件描述符上显式测试文件结束时遇到的问题,导致程序看起来挂起。

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

for 循环的条件表达式获得与 while 循环的条件表达式相同的 readline 等的特殊处理。

Foreach 循环

foreach 循环遍历一个普通列表值,并将标量变量 VAR 依次设置为列表的每个元素。如果变量之前带关键字 my,则它具有词法作用域,因此仅在循环内可见。否则,该变量隐式地局部于循环,并且在退出循环后重新获得其先前值。如果该变量之前已使用 my 声明,则它将使用该变量而不是全局变量,但它仍然局部于循环。这种隐式局部化发生在非 C 风格的循环中。

foreach 关键字实际上是 for 关键字的同义词,因此你可以使用任一关键字。如果省略 VAR,则 $_ 将设置为每个值。

如果 LIST 的任何元素是左值,则可以通过在循环内修改 VAR 来修改它。相反,如果 LIST 的任何元素都不是左值,则任何修改该元素的尝试都将失败。换句话说,foreach 循环索引变量是循环遍历的列表中每个项目的隐式别名。

如果 LIST 的任何部分是数组,则如果你在循环体中添加或删除元素(例如使用 splice),foreach 将会非常混乱。所以不要这样做。

如果 VAR 是绑定变量或其他特殊变量,则 foreach 可能不会执行你期望的操作。也不要这样做。

从 Perl 5.22 开始,此循环有一个实验性变体,它接受 VAR 前带有反斜杠的变量,在这种情况下,LIST 中的项目必须是引用。带反斜杠的变量将成为 LIST 中每个引用项的别名,该项必须是正确类型。在这种情况下,该变量不必是标量,反斜杠后面可以跟 my。要使用此形式,你必须通过 use feature 启用 refaliasing 功能。(请参阅 feature。另请参阅 perlref 中的“分配给引用”。)

从 Perl 5.36 开始,你可以一次迭代多个值。你只能使用词法标量作为迭代器变量进行迭代 - 与列表赋值不同,不能使用 undef 来表示不需要的值。这是当前实现的一个限制,将来可能会更改。

如果 LIST 的大小不是迭代器变量数量的倍数,则在最后一次迭代中,“多余”的迭代器变量将是 undef 的别名,就像 LIST 已追加 , undef 尽可能多次,使其长度成为精确的倍数。无论 LIST 是文字 LIST 还是数组,都会发生这种情况 - 即如果数组的大小不是迭代大小的倍数,则不会扩展数组,这与一次迭代一个数组一致。由于这些填充元素不是左值,因此尝试修改它们将失败,这与使用文字 undef 迭代列表时的行为一致。如果这不是你想要的行为,那么在循环开始之前,显式地将数组扩展为精确的倍数,或显式地抛出异常。

示例

for (@ary) { s/foo/bar/ }

for my $elem (@elements) {
    $elem *= 2;
}

for $count (reverse(1..10), "BOOM") {
    print $count, "\n";
    sleep(1);
}

for (1..15) { print "Merry Christmas\n"; }

foreach $item (split(/:[\\\n:]*/, $ENV{TERMCAP})) {
    print "Item: $item\n";
}

use feature "refaliasing";
no warnings "experimental::refaliasing";
foreach \my %hash (@array_of_hash_references) {
    # do something with each %hash
}

foreach my ($foo, $bar, $baz) (@list) {
    # do something three-at-a-time
}

foreach my ($key, $value) (%hash) {
    # iterate over the hash
    # The hash is immediately copied to a flat list before the loop
    # starts. The list contains copies of keys but aliases of values.
    # This is the same behaviour as for $var (%hash) {...}
}

以下是 C 程序员如何用 Perl 编码特定算法

for (my $i = 0; $i < @ary1; $i++) {
    for (my $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: for my $wid (@ary1) {
INNER:   for my $jet (@ary2) {
            next OUTER if $wid > $jet;
            $wid += $jet;
         }
      }

看看这样是不是容易多了?它更简洁、更安全、更快。它更简洁,因为更少噪音。它更安全,因为如果以后在内层和外层循环之间添加代码,新代码不会被意外执行。next 明确地迭代另一个循环,而不是仅仅终止内层循环。而且它更快,因为 Perl 执行 foreach 语句比执行等效的 C 风格 for 循环更快。

敏锐的 Perl 黑客可能已经注意到,for 循环有一个返回值,并且可以通过将循环包装在 do 块中来捕获此值。此发现的回报是此警告建议:for 循环的返回值未指定,并且可能会在不通知的情况下更改。不要依赖它。

尝试捕获异常处理

try/catch 语法提供与异常处理相关的控制流。try 关键字引入一个块,该块在遇到时将被执行,而 catch 块提供代码来处理第一个块可能抛出的任何异常。

try {
    my $x = call_a_function();
    $x < 100 or die "Too big";
    send_output($x);
}
catch ($e) {
    warn "Unable to output a value; $e";
}
print "Finished\n";

此处,如果初始块调用条件 die,或它调用的任何函数抛出未捕获的异常,则将执行 catch 块的主体(即 warn 语句)。在这种情况下,catch 块可以检查 $e 词法变量以查看异常是什么。如果未抛出异常,则 catch 块不会发生。在任何情况下,执行都将从以下语句继续 - 在此示例中为 print

catch 关键字后面必须紧跟括号中的变量声明,该声明会引入一个新变量,该变量对后续块的主体可见。在此块中,此变量将包含 try 块中的代码抛出的异常值。声明此变量时不必使用 my 关键字;这是隐含的(类似于子例程签名)。

trycatch 块都允许包含控制流表达式,例如 returngotonext/last/redo。在所有情况下,它们的行为都符合预期,没有警告。特别是,try 块中的 return 表达式将使其包含的整个函数返回 - 这与它在 eval 块中的行为形成对比,在 eval 块中,它只会使该块返回。

与其他控制流语法一样,当 trycatch 作为函数或 do 块中的最终语句放置时,它们将产生最后评估的值。这允许使用语法来创建值。在这种情况下,请记住不要使用 return 表达式,否则会导致包含的函数返回。

my $value = do {
    try {
        get_thing(@args);
    }
    catch ($e) {
        warn "Unable to get thing - $e";
        $DEFAULT_THING;
    }
};

与其他控制流语法一样,try 块对 caller() 不可见(例如,whileforeach 循环也是如此)。caller 结果的后续级别可以看到子例程调用和 eval 块,因为这些会影响 return 的工作方式。由于 try 块不会拦截 return,因此 caller 对它们不感兴趣。

trycatch 块可以根据需要在 finally 关键字引入的第三个块后面。这个第三个块在构造的其余部分完成之后执行。

try {
    call_a_function();
}
catch ($e) {
    warn "Unable to call; $e";
}
finally {
    print "Finished\n";
}

finally 块等同于使用 defer 块,并且会在相同情况下调用;无论 try 块是否成功完成、抛出异常,还是通过使用 return、循环控制或 goto 将控制权转移到其他地方。

trycatch 块不同,finally 块不允许 returngoto 或使用任何循环控制。最终表达式值会被忽略,并且即使它被放置在函数的最后,也不会影响包含函数的返回值。

此语法目前处于实验阶段,必须使用 use feature 'try' 启用。它会在 experimental::try 类别中发出警告。

基本 BLOCK

一个 BLOCK 本身(标记或不标记)在语义上等同于执行一次的循环。因此,你可以在其中使用任何循环控制语句来离开或重新启动块。(请注意,在 eval{}sub{} 或与流行观念相反的 do{} 块中,不是这样,它们算作循环。)continue 块是可选的。

BLOCK 构造可用于模拟 case 结构。

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

你还会发现 foreach 循环用于创建主题化器和开关

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

此类构造经常使用,既是因为旧版本的 Perl 没有官方的 switch 语句,也因为下面立即描述的新版本仍然处于实验阶段,有时会令人困惑。

defer 块

defer 修饰符作为前缀的块提供了一段代码,该代码在作用域退出期间的稍后时间运行。

defer 块可以在允许常规块或其他语句的任何位置出现。如果执行流到达此语句,则块的主体会存储起来以供稍后使用,但不会立即调用。当控制流因任何原因离开包含块时,此存储的块会在经过时执行。它提供了一种将执行延迟到稍后的时间的方法。这类似于某些其他语言提供的语法,通常使用名为 try / finally 的关键字。

如果启用名为 defer 的功能,则可以使用此语法,并且目前处于实验阶段。如果启用实验警告,则在使用时会发出警告。

use feature 'defer';

{
    say "This happens first";
    defer { say "This happens last"; }

    say "And this happens inbetween";
}

如果单个作用域中包含多个 defer 块,则它们将按 LIFO 顺序执行;最后到达的块将首先执行。

由于常规直通、显式 returndie 引发的异常或其调用的函数传播的异常、goto 或任何循环控制语句 nextlastredo,当控制离开其包含的块时,将调用由 defer 块存储的代码。

如果控制流未到达 defer 语句本身,则其主体不会存储以供以后执行。(这与 END 阶段块提供的代码形成鲜明对比,无论执行是否到达其所在的代码行,该代码始终由编译器排队。)

use feature 'defer';

{
    defer { say "This will run"; }
    return;
    defer { say "This will not"; }
}

defer 块中代码引发的异常将以与普通代码引发的任何其他异常相同的方式传播到调用方。

如果由于引发异常而执行 defer 块并引发另一个异常,则未指定会发生什么,除了调用方肯定会收到异常。

除了引发异常之外,不允许 defer 块以其他方式更改其周围代码的控制流。特别是,它可能不会导致其包含的函数 return,也不可能 goto 标签,或使用 nextlastredo 控制包含的循环。但是,这些构造完全允许在 defer 的主体中。

use feature 'defer';

{
    defer {
        foreach ( 1 .. 5 ) {
            last if $_ == 3;     # this is permitted
        }
    }
}

{
    foreach ( 6 .. 10 ) {
        defer {
            last if $_ == 8;     # this is not
        }
    }
}

Switch 语句

从 Perl 5.10.1 开始(好吧,5.10.0,但它不能正常工作),你可以说

use feature "switch";

以启用实验性 switch 功能。这大致基于 Raku 提议的旧版本,但它不再类似于 Raku 构造。每当你声明你的代码更喜欢在 5.10 到 5.34 之间的 Perl 版本下运行时,你也会获得 switch 功能。例如

use v5.14;

在“switch”功能下,Perl 获得了实验关键字 givenwhendefaultcontinuebreak。从 Perl 5.16 开始,可以使用 CORE:: 为 switch 关键字添加前缀,以在没有 use feature 语句的情况下访问该功能。关键字 givenwhen 类似于其他语言中的 switchcase——尽管 continue 不是——因此上一节中的代码可以重写为

use v5.10.1;
for ($var) {
    when (/^abc/) { $abc = 1 }
    when (/^def/) { $def = 1 }
    when (/^xyz/) { $xyz = 1 }
    default       { $nothing = 1 }
}

foreach 是设置主题化的非实验性方法。如果您希望使用高度实验性的 given,可以这样编写

use v5.10.1;
given ($var) {
    when (/^abc/) { $abc = 1 }
    when (/^def/) { $def = 1 }
    when (/^xyz/) { $xyz = 1 }
    default       { $nothing = 1 }
}

从 5.14 开始,还可以这样编写

use v5.14;
for ($var) {
    $abc = 1 when /^abc/;
    $def = 1 when /^def/;
    $xyz = 1 when /^xyz/;
    default { $nothing = 1 }
}

或者如果您不在乎安全起见,可以这样编写

use v5.14;
given ($var) {
    $abc = 1 when /^abc/;
    $def = 1 when /^def/;
    $xyz = 1 when /^xyz/;
    default { $nothing = 1 }
}

givenwhen 的参数处于标量上下文中,并且 given$_ 变量分配给其主题值。

whenEXPR 参数的确切作用很难准确描述,但通常情况下,它会尝试猜测您希望完成的操作。有时它被解释为 $_ ~~ EXPR,有时则不然。当由 given 块词法包围时,它的行为也与由 foreach 循环动态包围时不同。这些规则太难理解,无法在此处描述。请参阅稍后的 “关于 given 和 when 的实验性详细信息”

由于在 Perl 5.10 和 5.16 之间实现 given 的方式中存在一个不幸的错误,在这些实现中,由 given 控制的 $_ 版本仅仅是原始版本的词法作用域副本,而不是原始版本的动态作用域别名,就像它是 foreach 或在原始和当前 Raku 语言规范下一样。此错误已在 Perl 5.18 中修复(并且词法化的 $_ 本身已在 Perl 5.24 中删除)。

如果您的代码仍然需要在较旧版本上运行,请坚持使用 foreach 作为主题化,您会更不高兴。

Goto

虽然不适合胆小的人,但 Perl 确实支持 goto 语句。有三种形式:goto-LABEL、goto-EXPR 和 goto-&NAME。循环的 LABEL 实际上不是 goto 的有效目标;它只是循环的名称。

goto-LABEL 形式查找标记为 LABEL 的语句,并在那里恢复执行。它不能用于进入需要初始化的任何构造,例如子例程或 foreach 循环。它也不能用于进入被优化掉的构造。它几乎可以在动态作用域内的任何其他地方使用,包括在子例程之外,但通常最好使用其他一些构造,例如 lastdie。Perl 的作者从未觉得有必要使用这种形式的 goto(在 Perl 中,即 C 是另一回事)。

goto-EXPR 形式需要一个标签名称,其作用域将被动态解析。这允许根据 FORTRAN 进行计算的 goto,但如果您要优化可维护性,则不一定推荐

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

goto-&NAME 形式非常神奇,它将当前正在运行的子例程替换为对指定子例程的调用。AUTOLOAD() 子例程使用它来加载另一个子例程,然后假装从一开始就调用了另一个子例程(除了当前子例程中对 @_ 的任何修改都将传播到另一个子例程)。在 goto 之后,即使是 caller() 也无法分辨出该例程是首先被调用的。

在几乎所有这样的情况下,通常最好使用 nextlastredo 的结构化控制流机制,而不是诉诸于 goto。对于某些应用程序,eval{} 和 die() 的 catch 和 throw 对用于异常处理也可以是一种审慎的方法。

省略号语句

从 Perl 5.12 开始,Perl 接受省略号“...”作为尚未实现的代码的占位符。当 Perl 5.12 或更高版本遇到省略号语句时,它会无错误地解析它,但是如果你实际尝试执行它,Perl 会抛出一个带有文本 Unimplemented 的异常

use v5.12;
sub unimplemented { ... }
eval { unimplemented() };
if ($@ =~ /^Unimplemented at /) {
    say "I found an ellipsis!";
}

你只能使用省略号语句来表示一个完整的语句。句法上,“...;”是一个完整的语句,但是,与其他类型的以分号结尾的语句一样,如果“...”出现在闭合大括号之前,则可以省略分号。以下示例显示了省略号的工作原理

use v5.12;
{ ... }
sub foo { ... }
...;
eval { ... };
sub somemeth {
    my $self = shift;
    ...;
}
$x = do {
    my $n;
    ...;
    say "Hurrah!";
    $n;
};

省略号语句不能代替作为更大语句一部分的表达式。这些尝试使用省略号的示例是语法错误

use v5.12;

print ...;
open(my $fh, ">", "/dev/passwd") or ...;
if ($condition && ... ) { say "Howdy" };
... if $a > $b;
say "Cromulent" if ...;
$flub = 5 + ...;

在某些情况下,Perl 无法立即区分表达式和语句。例如,除非大括号中有一些内容可以给 Perl 一个提示,否则块和匿名哈希引用构造函数的语法看起来是相同的。如果 Perl 没有猜测 { ... } 是一个块,则省略号是一个语法错误。在你的块中,你可以在省略号之前使用 ; 来表示 { ... } 是一个块,而不是一个哈希引用构造函数。

注意:有些人习惯性地将这部分标点符号称为“yada-yada”或“三点”,但它的真实名称实际上是省略号。

POD:嵌入式文档

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 转义符。这意味着以下机密内容将被编译器和转换器忽略。

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

您可能不应该永远依赖于 warn() 被 pod 化。并非所有 pod 转换器在这方面都表现良好,而且编译器可能会变得更加挑剔。

还可以使用 pod 指令快速注释掉一段代码。

纯旧注释(不是!)

Perl 可以处理行指令,就像 C 预处理器一样。使用此功能,可以在错误或警告消息中控制 Perl 对文件名和行号的理解(特别是对于使用 eval() 处理的字符串)。此机制的语法与大多数 C 预处理器的语法几乎相同:它与正则表达式相匹配

# example: '# line 42 "new_filename.plx"'
/^\#   \s*
  line \s+ (\d+)   \s*
  (?:\s("?)([^"]+)\g2)? \s*
 $/x

其中 $1 是下一行的行号,$3 是可选文件名(带或不带引号指定)。请注意,与现代 C 预处理器不同,# 前面不能有空格。

行指令中包含一个相当明显的陷阱:调试器和分析器只会显示在给定文件中特定行号出现的最后一行源代码。应注意不要在以后要调试的代码中导致行号冲突。

以下是一些您应该能够在命令 shell 中键入的示例

% perl
# line 200 "bzzzt"
# the '#' on the previous line must be the first char on line
die 'foo';
__END__
foo at bzzzt line 201.

% perl
# line 200 "bzzzt"
eval qq[\n#line 2001 ""\ndie 'foo']; print $@;
__END__
foo at - line 2001.

% perl
eval qq[\n#line 200 "foo bar"\ndie 'foo']; print $@;
__END__
foo at foo bar line 200.

% perl
# line 345 "goop"
eval "\n#line " . __LINE__ . ' "' . __FILE__ ."\"\ndie 'foo'";
print $@;
__END__
foo at goop line 345.

关于 given 和 when 的实验详细信息

如前所述,“switch”功能被认为是高度实验性的(它也计划在 perl 5.42.0 中删除);它可能会在没有太多通知的情况下发生变化。特别是,when 具有棘手的行为,预计将来会发生变化,变得不那么棘手。不要依赖于它当前的(错误)实现。在 Perl 5.18 之前,given 也有棘手的行为,如果您必须在旧版本的 Perl 上运行代码,您仍然应该注意这些行为。

以下是一个 given 的较长示例

use feature ":5.10";
given ($foo) {
    when (undef) {
        say '$foo is undefined';
    }
    when ("foo") {
        say '$foo is the string "foo"';
    }
    when ([1,3,5,7,9]) {
        say '$foo is an odd digit';
        continue; # Fall through
    }
    when ($_ < 100) {
        say '$foo is numerically less than 100';
    }
    when (\&complicated_check) {
        say 'a complicated check for $foo is true';
    }
    default {
        die q(I don't know what to do with $foo);
    }
}

在 Perl 5.18 之前,given(EXPR)EXPR 的值仅分配给 $_ 的词法作用域 副本(!),而不是像 foreach 那样分配给动态作用域别名。这使其类似于

do { my $_ = EXPR; ... }

除了块被成功的 when 或显式的 break 自动中断。因为它只是一个副本,并且因为它仅是词法作用域,而不是动态作用域,所以你无法对其执行在 foreach 循环中习惯执行的操作。特别是,如果这些函数可能尝试访问 $_,它不适用于任意函数调用。最好坚持使用 foreach

大部分功能来自有时可以应用的隐式智能匹配。大多数情况下,when(EXPR) 被视为 $_ 的隐式智能匹配,即 $_ ~~ EXPR。(有关智能匹配的更多信息,请参阅 perlop 中的 “智能匹配运算符”。)但是,当 EXPR 是下面列出的 10 个特殊情况(或类似情况)之一时,它直接用作布尔值。

1.

用户定义的子例程调用或方法调用。

2.

形式为 /REGEX/$foo =~ /REGEX/$foo =~ EXPR 的正则表达式匹配。此外,形式为 !/REGEX/$foo !~ /REGEX/$foo !~ EXPR 的否定正则表达式匹配。

3.

使用显式 ~~ 运算符的智能匹配,例如 EXPR ~~ EXPR

注意:你通常必须使用 $c ~~ $_,因为默认情况下使用 $_ ~~ $c,这通常与你想要的结果相反。

4.

布尔比较运算符,例如 $_ < 10$x eq "abc"。此规则适用的关系运算符有六个数字比较(<><=>===!=)和六个字符串比较(ltgtlegeeqne)。

5.

至少有三个内置函数 defined(...)exists(...)eof(...)。如果我们想到更多,我们可能会稍后添加更多。

6.

否定表达式,无论 !(EXPR) 还是 not(EXPR),或逻辑异或,(EXPR1) xor (EXPR2)。不包括按位版本(~^)。

7.

一个文件测试运算符,有 4 个例外:-s-M-A-C,因为它们返回数值,而不是布尔值。-z 文件测试运算符不包含在例外列表中。

8.

..... 翻转运算符。请注意,... 翻转运算符与刚才描述的 ... 省略语句完全不同。

在上述 8 种情况下,EXPR 的值直接用作布尔值,因此不会执行智能匹配。你可以将 when 视为一个智能智能匹配。

此外,Perl 会检查逻辑运算符的操作数,以通过对操作数应用上述测试来决定是否对每个操作数使用智能匹配

9.

如果 EXPR 为 EXPR1 && EXPR2EXPR1 and EXPR2,则会将测试递归应用于 EXPR1 和 EXPR2。仅当两个操作数也通过测试(递归)时,表达式才会被视为布尔值。否则,将使用智能匹配。

10.

如果 EXPR 为 EXPR1 || EXPR2EXPR1 // EXPR2EXPR1 or EXPR2,则仅将测试递归应用于 EXPR1(它本身可能是一个优先级更高的 AND 运算符,例如,因此受制于前一条规则),而不应用于 EXPR2。如果 EXPR1 要使用智能匹配,则 EXPR2 也这样做,无论 EXPR2 包含什么内容。但如果 EXPR2 不能使用智能匹配,则第二个参数也不会使用。这与刚才描述的 && 情况完全不同,因此请小心。

这些规则很复杂,但其目标是按照你的意愿执行操作(即使你不太理解它们为何这样做)。例如

when (/^\d+$/ && $_ < 75) { ... }

将被视为布尔值匹配,因为规则规定正则表达式匹配和对 $_ 的显式测试都将被视为布尔值。

此外

when ([qw(foo bar)] && /baz/) { ... }

将使用智能匹配,因为只有一个操作数是布尔值:另一个使用智能匹配,并且获胜。

进一步

when ([qw(foo bar)] || /^baz/) { ... }

将使用智能匹配(仅考虑第一个操作数),而

when (/^baz/ || [qw(foo bar)]) { ... }

将仅测试正则表达式,这会导致两个操作数都被视为布尔值。因此,请注意这一点,因为数组引用始终为真值,这实际上使其变得多余。不是一个好主意。

同义重复的布尔运算符仍然会被优化掉。不要尝试编写

when ("foo" or "bar") { ... }

这将优化为 "foo",因此 "bar" 永远不会被考虑(即使规则规定对 "foo" 使用智能匹配)。对于这样的交替,数组引用将起作用,因为这将引发智能匹配

when ([qw(foo bar)] { ... }

这在某种程度上等同于 C 样式 switch 语句的贯穿功能(不要与 Perl 的贯穿功能混淆——见下文),其中同一个块用于多个 case 语句。

另一个有用的快捷方式是,如果你使用一个文字数组或哈希作为 given 的参数,它将变成一个引用。因此,例如,given(@foo)given(\@foo) 相同。

default 的行为与 when(1 == 1) 完全相同,也就是说它始终匹配。

退出

你可以使用 break 关键字退出封闭的 given 块。每个 when 块都隐式地以 break 结束。

贯穿

你可以使用 continue 关键字从一个 case 贯穿到下一个紧邻的 whendefault

given($foo) {
    when (/x/) { say '$foo contains an x'; continue }
    when (/y/) { say '$foo contains a y'            }
    default    { say '$foo does not contain a y'    }
}

返回值

当一个 given 语句也是一个有效的表达式时(例如,当它是块的最后一个语句时),它将求值为

在这两种情况下,最后一个表达式都是在应用于 given 块的上下文中求值的。

请注意,与 ifunless 不同,失败的 when 语句始终求值为一个空列表。

my $price = do {
    given ($item) {
        when (["pear", "apple"]) { 1 }
        break when "vote";      # My vote cannot be bought
        1e10  when /Mona Lisa/;
        "unknown";
    }
};

目前,given 块不能始终用作正确的表达式。这可能会在 Perl 的未来版本中得到解决。

在循环中切换

你可以使用 foreach() 循环,而不是使用 given()。例如,以下是一种计算特定字符串在数组中出现次数的方法

use v5.10.1;
my $count = 0;
for (@array) {
    when ("foo") { ++$count }
}
print "\@array contains $count copies of 'foo'\n";

或在较新版本中

use v5.14;
my $count = 0;
for (@array) {
    ++$count when "foo";
}
print "\@array contains $count copies of 'foo'\n";

在所有 when 块的末尾,都有一个隐式的 next。如果你只对第一个匹配感兴趣,你可以用一个显式的 last 覆盖它。

如果你明确指定了一个循环变量,如 for $item (@array),则此方法不起作用。你必须使用默认变量 $_

与 Raku 的区别

Perl 5 的 smartmatch 和 given/when 构造与它们的 Raku 类似物不兼容。最明显且最不重要的区别是,在 Perl 5 中,given()when() 的参数周围需要括号(除了当后者用作语句修饰符时)。在 Raku 中,括号在控制构造(如 if()while()when())中始终是可选的;它们不能在 Perl 5 中变成可选的,而不会造成很大的潜在混乱,因为 Perl 5 将解析表达式

given $foo {
    ...
}

就好像 given 的参数是哈希 %foo 的一个元素,将花括号解释为哈希元素语法。

但是,还有许多其他差异。例如,这在 Perl 5 中有效

use v5.12;
my @primary = ("red", "blue", "green");

if (@primary ~~ "red") {
    say "primary smartmatches red";
}

if ("red" ~~ @primary) {
    say "red smartmatches primary";
}

say "that's all, folks!";

但在 Raku 中根本不起作用。相反,您应该使用(可并行的)any 运算符

if any(@primary) eq "red" {
    say "primary smartmatches red";
}

if "red" eq any(@primary) {
    say "red smartmatches primary";
}

perlop 中的“Smartmatch 运算符” 中的 smartmatch 表与 Raku 规范提出的表不同,这主要是由于 Raku 和 Perl 5 的数据模型之间的差异,但也因为 Raku 规范在 Perl 5 匆忙采用早期版本后发生了变化。

在 Raku 中,when() 将始终对其参数执行隐式 smartmatch,而在 Perl 5 中,在各种松散定义的情况下抑制此隐式 smartmatch 既方便(尽管可能令人困惑),如上所述。(差异很大程度上是因为 Perl 5 甚至在内部都没有布尔类型。)