perlsyn - Perl 語法
Perl 程式由一系列從上到下執行的宣告和陳述組成。迴圈、子常式和其他控制結構允許您在程式碼中跳躍。
Perl 是一種自由格式語言:您可以隨意設定格式和縮排。空白主要用於分隔符號,這與 Python 等語言不同,在 Python 中空白是語法的重要組成部分,或 Fortran,在 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 中的 "Scalar values"。
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;
while
和 until
修飾詞具有通常的「while
迴圈」語意(條件先評估),但當套用於 do
-BLOCK(或 Perl4 do
-SUBROUTINE 陳述式)時除外,在這種情況下,區塊會在評估條件之前執行一次。
這樣就可以撰寫像這樣的迴圈
do {
$line = <STDIN>;
...
} until !defined($line) || $line eq ".\n"
請參閱 perlfunc 中的「do」。另請注意,稍後描述的迴圈控制陳述式將不會在此建構中運作,因為修飾詞不採用迴圈標籤。抱歉。您隨時可以在其內部(對於 next
/redo
)或其周圍(對於 last
)放置另一個區塊來執行此類操作。
對於 next
或 redo
,只需將大括號加倍
do {{
next if $x == $y;
# do something here
}} until $x++ > $z;
對於 last
,您必須更詳細,並在周圍放置大括號
{
do {
last if $x == $y**2;
# do something here
} while $x++ <= $z;
}
如果您需要 next
和 last
,您必須執行兩者,並使用迴圈標籤
LOOP: {
do {{
next if $x == $y;
last LOOP if $x == $y**2;
# do something here
}} until $x++ > $z;
}
注意:使用陳述式修飾詞條件或迴圈建構(例如,my $x if ...
)修改的 my
、state
或 our
的行為是未定義的。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
之後可以接 else
。unless
甚至可以接一個或多個 elsif
陳述式,不過您可能需要三思而後行,再使用這個特定的語言結構,因為每位閱讀您程式碼的人在了解發生什麼事之前,都必須思考至少兩次。
while
語句會在表達式為真時執行區塊。until
語句會在表達式為假時執行區塊。LABEL 是選用的,如果存在,則由一個識別碼和一個冒號組成。LABEL 會識別迴圈控制語句 next
、last
和 redo
的迴圈。如果省略 LABEL,迴圈控制語句會指涉最內層的封閉迴圈。這可能會在執行時動態搜尋你的呼叫堆疊,以尋找 LABEL。如果你使用 use warnings
實用程式或 -w 旗標,這種絕望的行為會觸發警告。
如果 while
語句的條件表達式是根據一組反覆表達式類型中的任何一個,則會獲得一些神奇的處理。受影響的反覆表達式類型為 readline
、<FILEHANDLE>
輸入運算子、readdir
、glob
、<PATTERN>
搜尋運算子,以及 each
。如果條件表達式是這些表達式類型之一,則反覆運算子產生的值會隱含地指定給 $_
。如果條件表達式是這些表達式類型之一,或將其中一個明確指定給標量,則條件實際上會測試表達式值的定義,而不是它的常規真值。
如果有一個 continue
BLOCK,它總會在條件即將再次評估之前執行。因此,即使迴圈已透過 next
語句繼續,它仍可用於遞增迴圈變數。
當一個區塊之前有一個編譯階段關鍵字,例如 BEGIN
、END
、INIT
、CHECK
或 UNITCHECK
,則區塊只會在對應的執行階段執行。請參閱 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
區塊,它只會在 regex 捨棄的行執行(因為 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?
}
如果將 while
字詞替換為 until
字詞,測試的意義會反轉,但條件仍會在第一次迭代之前測試。
迴圈控制陳述式不會在 if
或 unless
中運作,因為它們不是迴圈。不過,您可以加倍大括號來讓它們變成迴圈。
if (/pattern/) {{
last if /fred/;
next if /barney/; # same effect as "last",
# but doesn't document as well
# do something here
}}
這是因為區塊本身會作為執行一次的迴圈,請參閱 「基本 BLOCK」。
Perl 4 中可用的 while/if BLOCK BLOCK
形式不再可用。將 if BLOCK
的任何出現取代為 if (do BLOCK)
。
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
迴圈)中的測試為空,它會被視為 true。也就是說,這兩者
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
迴圈會對常態清單值進行反覆運算,並將純量變數 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
關鍵字來宣告此變數;這是暗示的(類似於子常式簽章)。
try
和 catch
區塊都允許包含控制流程表示式,例如 return
、goto
或 next
/last
/redo
。在所有情況下,它們都會如預期般執行,且不會產生警告。特別是,try
區塊中的 return
表示式會讓其包含的整個函式回傳,這與它在 eval
區塊中的行為不同,後者只會讓該區塊回傳。
與其他控制流程語法一樣,try
和 catch
會在作為函式或 do
區塊中的最後一個陳述式時產生最後一個評估值。這允許使用此語法來建立值。在此情況下,請記住不要使用 return
表示式,否則會導致包含函式回傳。
my $value = do {
try {
get_thing(@args);
}
catch ($e) {
warn "Unable to get thing - $e";
$DEFAULT_THING;
}
};
與其他控制流程語法一樣,try
區塊對 caller()
不可見(例如,while
或 foreach
迴圈也不可見)。caller
結果的後續層級可以看到子常式呼叫和 eval
區塊,因為它們會影響 return
的運作方式。由於 try
區塊不會攔截 return
,因此 caller
對它們不感興趣。
try
和 catch
區塊可以選擇在 finally
關鍵字後面加上第三個區塊。這個第三個區塊會在結構的其餘部分執行完畢後執行。
try {
call_a_function();
}
catch ($e) {
warn "Unable to call; $e";
}
finally {
print "Finished\n";
}
finally
區塊等同於使用 defer
區塊,並且會在相同的情況下呼叫;無論 try
區塊是否成功完成、拋出例外或使用 return
、迴圈控制或 goto
將控制權轉移到其他地方。
與 try
和 catch
區塊不同,finally
區塊不允許 return
、goto
或使用任何迴圈控制。最後的表示值會被忽略,即使它放在函式的最後面,也不會影響包含函式的回傳值。
此語法目前為實驗性質,必須使用 use feature 'try'
啟用。它會在 experimental::try
類別中發出警告。
單獨的 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
區塊可以出現在允許一般區塊或其他語法的任何點。如果執行流程到達此語法,區塊的主體會儲存起來稍後執行,但不會立即呼叫。當控制流程因任何原因離開包含區塊時,這個儲存的區塊會在經過時執行。它提供一種方法,將執行延後到稍後時間。這與某些其他語言提供的語法類似,通常使用名為 try / finally
的關鍵字。
此語法在啟用名為 defer
的功能時可用,目前為實驗性質。如果啟用實驗警告,使用時會發出警告。
use feature 'defer';
{
say "This happens first";
defer { say "This happens last"; }
say "And this happens inbetween";
}
如果單一範圍內包含多個 defer
區塊,它們會以 LIFO 順序執行;最後一個達到的會先執行。
defer
區塊儲存的程式碼會在控制離開其包含區塊時呼叫,原因可能是正常執行、明確的 return
、die
引發的例外或由其呼叫的函式傳播的例外、goto
,或任何迴圈控制陳述式 next
、last
或 redo
。
如果控制流程未到達 defer
陳述式本身,則其主體不會儲存以供稍後執行。(這與 END
phaser 區塊提供的程式碼形成直接對比,後者始終由編譯器排入佇列,無論執行是否曾到達其所在的列。)
use feature 'defer';
{
defer { say "This will run"; }
return;
defer { say "This will not"; }
}
defer
區塊內程式碼引發的例外會以與正常程式碼引發的任何其他例外相同的方式傳播到呼叫者。
如果 defer
區塊因引發的例外而執行,並引發另一個例外,則未明確指定會發生什麼,但呼叫者肯定會收到例外。
除了引發例外之外,defer
區塊不得以其他方式變更其周圍程式碼的控制流程。特別是,它可能不會導致其包含函式 return
,也不能 goto
標籤,或使用 next
、last
或 redo
控制包含迴圈。不過,這些結構完全允許在 defer
的主體內。
use feature 'defer';
{
defer {
foreach ( 1 .. 5 ) {
last if $_ == 3; # this is permitted
}
}
}
{
foreach ( 6 .. 10 ) {
defer {
last if $_ == 8; # this is not
}
}
}
從 Perl 5.10.1 開始(好吧,是 5.10.0,但它沒有正常運作),您可以說
use feature "switch";
啟用實驗性 switch 功能。這大致基於 Raku 提案的舊版本,但不再類似 Raku 結構。每當宣告您的程式碼偏好於在 Perl 5.10 至 5.34 版本下執行時,您也會取得 switch 功能。例如
use v5.14;
在「switch」功能下,Perl 會獲得實驗性關鍵字 given
、when
、default
、continue
和 break
。從 Perl 5.16 開始,可以使用 CORE::
為 switch 關鍵字加上前綴,以在沒有 use feature
陳述式的情況下存取該功能。關鍵字 given
和 when
類似於其他語言中的 switch
和 case
(儘管 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 }
}
given
和 when
的引數在純量內容中,而 given
會將 $_
變數指定給它的主題值。
要精確描述 when
的 EXPR 引數實際上做了什麼很困難,但一般來說,它會嘗試猜測您想要執行的動作。有時它會被解釋為 $_ ~~ EXPR
,有時則不會。當由 given
區塊以詞彙方式封裝時,它的行為也不同於由 foreach
迴圈以動態方式封裝時。這些規則太難理解,無法在此處說明。請參閱後面的 「given 和 when 的實驗性詳細資料」。
由於 Perl 5.10 和 5.16 之間在 given
的實作方式中有一個不幸的錯誤,在這些實作下,由 given
管理的 $_
版本只是一個原件的詞彙範圍複本,而不是原件的動態範圍別名,就像它是 foreach
或在原版和目前的 Raku 語言規格下一樣。這個錯誤已在 Perl 5.18 中修正(而詞彙化的 $_
本身已在 Perl 5.24 中移除)。
如果您的程式碼仍需要在舊版本上執行,請堅持使用 foreach
作為您的主題化,您會比較不會感到不滿意。
儘管不適合膽小的人,Perl 確實支援 goto
陳述式。有三個形式:goto
-LABEL、goto
-EXPR 和 goto
-&NAME。迴圈的 LABEL 實際上不是 goto
的有效目標;它只是迴圈的名稱。
goto
-LABEL 形式會尋找標記為 LABEL 的陳述式,並從那裡繼續執行。它不能用於進入需要初始化的任何結構,例如子常式或 foreach
迴圈。它也不能用於進入已最佳化的結構。它可用於進入動態範圍內的幾乎任何其他地方,包括子常式之外,但通常最好使用其他結構,例如 last
或 die
。Perl 的作者從未覺得有必要使用這種形式的 goto
(在 Perl 中,這是 C 的另一回事)。
goto
-EXPR 形式需要標籤名稱,其範圍將動態解析。這允許根據 FORTRAN 計算 goto
,但如果您要最佳化可維護性,則不建議使用
goto(("FOO", "BAR", "GLARCH")[$i]);
goto
-&NAME 形式非常神奇,並用名為子常式的呼叫取代目前正在執行的子常式。這是由 AUTOLOAD()
子常式使用的,它希望載入另一個子常式,然後假裝一開始就已呼叫另一個子常式(但對目前子常式中 @_
的任何修改都會傳播到另一個子常式。)在 goto
之後,甚至 caller()
都無法得知這個常式首先被呼叫。
在幾乎所有這種情況下,通常最好使用 next
、last
或 redo
的結構化控制流程機制,而不是訴諸 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」或「三點」,但其真實名稱實際上是省略號。
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 預處理器不同,#
前面不能有空白。
行指令中包含一個相當明顯的陷阱:偵錯器和分析器只會顯示在特定檔案中特定行號出現的最後一個原始碼行。應注意不要在稍後要偵錯的程式碼中造成行號衝突。
以下是您應該能夠輸入到命令殼層的一些範例
% 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.
如前所述,「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 個例外情況(或類似情況)之一時,它會直接用作布林值。
使用者定義的子常式呼叫或方法呼叫。
/REGEX/
、$foo =~ /REGEX/
或 $foo =~ EXPR
形式的正規表示式比對。此外,!/REGEX/
、$foo !~ /REGEX/
或 $foo !~ EXPR
形式的否定正規表示式比對。
使用明確 ~~
運算子的智慧比對,例如 EXPR ~~ EXPR
。
注意:你通常必須使用 $c ~~ $_
,因為預設情況使用 $_ ~~ $c
,這通常與你想要的相反。
布林比較運算子,例如 $_ < 10
或 $x eq "abc"
。此處適用的關係運算子有六個數字比較(<
、>
、<=
、>=
、==
和 !=
)和六個字串比較(lt
、gt
、le
、ge
、eq
和 ne
)。
至少有三個內建函式 defined(...)
、exists(...)
和 eof(...)
。日後我們可能會想到並加入更多此類函式。
否定表達式,無論是 !(EXPR)
或 not(EXPR)
,或是邏輯異或 (EXPR1) xor (EXPR2)
。不包含位元運算版本(~
和 ^
)。
檔案測試運算子,有四個例外:-s
、-M
、-A
和 -C
,因為它們會傳回數字值,而非布林值。-z
檔案測試運算子未包含在例外清單中。
..
和 ...
迴圈運算子。請注意,...
迴圈運算子與剛才描述的 ...
省略陳述式完全不同。
在上述八種情況中,EXPR 的值會直接用作布林值,因此不會執行智慧比對。您可以將 when
視為智慧智慧比對。
此外,Perl 會檢查邏輯運算子的運算元,以決定是否對每個運算元使用智慧比對,方法是將上述測試套用至運算元
如果 EXPR 為 EXPR1 && EXPR2
或 EXPR1 and EXPR2
,則會遞迴地將測試套用至 EXPR1 和 EXPR2。只有當兩個運算元也遞迴通過測試時,表達式才會被視為布林值。否則,會使用智慧比對。
如果 EXPR 為 EXPR1 || EXPR2
、EXPR1 // EXPR2
或 EXPR1 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
關鍵字從一個案例穿透到下一個立即的 when
或 default
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
語句也是一個有效的表達式(例如,當它是區塊的最後一個語句時),它會評估為
一旦遇到明確的 break
,就會出現一個空清單。
如果碰巧有一個,則為成功的 when
/default
子句的最後一個評估表達式的值。
如果沒有條件為真,則為 given
區塊的最後一個評估表達式的值。
在最後兩種情況下,最後一個表達式是在應用於 given
區塊的上下文中評估的。
請注意,與 if
和 unless
不同,失敗的 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)
),這將無法運作。您必須使用預設變數 $_
。
Perl 5 的智慧匹配和 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 中的智慧匹配算子" 中的智慧匹配表格與 Raku 規範提出的表格不同,主要是由於 Raku 和 Perl 5 的資料模型之間的差異,但也因為 Raku 規範自 Perl 5 匆忙採用早期版本以來已經改變。
在 Raku 中,when()
始終會對其引數執行隱含的智慧匹配,而在 Perl 5 中,在各種定義較寬鬆的情況下抑制此隱含的智慧匹配很方便(儘管可能會造成混淆),如上所述。(差異主要是因為 Perl 5 甚至在內部都沒有布林類型。)