eval
的所有形式都用於執行一個小的 Perl 程式,捕捉遇到的任何錯誤,讓它們不會讓呼叫程式當機。
沒有參數的純粹 eval
只是 eval EXPR
,其中表達式被理解為包含在 $_
中。因此只有兩個真正的 eval
形式;帶有 EXPR 的那個通常稱為「字串 eval」。在字串 eval 中,表達式的值(本身在標量上下文中確定)會先被解析,如果沒有錯誤,則會在當前 Perl 程式的詞彙上下文中作為一個區塊執行。此形式通常用於延遲解析和隨後執行 EXPR 的文字,直到執行時間。請注意,每次執行 eval
時都會解析值。
另一種形式稱為「區塊評估」。它比字串評估較不普遍,但 BLOCK 中的程式碼僅剖析一次(與剖析包含 eval
本身的程式碼同時進行),並在目前 Perl 程式的內容中執行。此形式通常用於比第一種更有效率地捕捉例外狀況,同時也提供在編譯時期檢查 BLOCK 中程式碼的優點。BLOCK 僅剖析和編譯一次。由於會捕捉錯誤,因此常被用來檢查特定功能是否可用。
在兩種形式中,傳回的值都是迷你程式中最後一個評估表達式的值;也可以使用 return 陳述式,就像子常式一樣。提供傳回值的表達式會在無效、純量或清單內容中評估,視 eval
本身的內容而定。請參閱 wantarray
,深入了解如何決定評估內容。
如果發生語法錯誤或執行時期錯誤,或執行 die
陳述式,eval
會在純量內容中傳回 undef
,或在清單內容中傳回空清單,而 $@
會設定為錯誤訊息。(在 5.16 之前,有一個錯誤會導致 undef
在語法錯誤中以清單內容傳回,但不會在執行時期錯誤中傳回。)如果沒有錯誤,$@
會設定為空字串。像 last
或 goto
等控制流程運算子可以略過 $@
的設定。請注意,使用 eval
既不會讓 Perl 停止將警告列印到 STDERR,也不會將警告訊息的文字填入 $@
。要執行這兩個動作,您必須使用 $SIG{__WARN__}
工具,或使用 no warnings 'all'
在 BLOCK 或 EXPR 中關閉警告。請參閱 warn
、perlvar 和 warnings。
請注意,因為 eval
會捕捉其他會造成致命錯誤的錯誤,因此它對於判斷特定功能(例如 socket
或 symlink
)是否已實作很有用。它也是 Perl 的例外捕捉機制,其中 die
運算子用於引發例外狀況。
在 Perl 5.14 之前,對 $@
的指定發生在還原區域變數之前,這表示您的程式碼若要在較舊的版本中執行,如果您想要遮蔽部分而非全部錯誤,則需要一個暫時變數
# alter $@ on nefarious repugnancy only
{
my $e;
{
local $@; # protect existing $@
eval { test_repugnancy() };
# $@ =~ /nefarious/ and die $@; # Perl 5.14 and higher only
$@ =~ /nefarious/ and $e = $@;
}
die $e if defined $e
}
針對每種形式有一些不同的考量
由於 EXPR 的傳回值在當前 Perl 程式的詞彙範圍內當作區塊執行,因此任何外部詞彙變數對它來說都是可見的,並且任何套件變數設定或子常式與格式定義都會保留下來。
請注意,當 BEGIN {}
區塊嵌入在 eval 區塊內時,區塊的內容將立即執行,並且在 eval 程式碼的其餘部分執行之前執行。您可以透過以下方式完全停用此功能:
local ${^MAX_NESTED_EVAL_BEGIN_BLOCKS} = 0;
eval $string;
這將導致 $string
中的任何嵌入式 BEGIN
區塊擲回例外狀況。
"unicode_eval"
功能下如果啟用此功能(在 use 5.16
或更高宣告下為預設值),Perl 會假設 EXPR 是字元字串。因此,字串中的任何 use utf8
或 no utf8
宣告都不會產生作用。來源篩選器也遭到禁止。(不過,unicode_strings
可以出現在字串中。)
另請參閱 evalbytes
算子,它可以正確地與來源篩選器搭配使用。
"unicode_eval"
功能之外在這種情況下,行為是有問題的,而且不容易描述。以下兩個錯誤無法在不中斷現有程式的情況下輕鬆修復
Perl 的 EXPR 內部儲存會影響執行程式碼的行為。例如
my $v = eval "use utf8; '$expr'";
如果 $expr 是 "\xc4\x80"
(UTF-8 中的 U+0100),則儲存在 $v
中的值將取決於 Perl 是否「升級」儲存 $expr(請參閱 utf8)
如果升級,$v
將會是 "\xc4\x80"
(亦即,use utf8
沒有作用。)
如果未升級,$v
將會是 "\x{100}"
。
這是不理想的,因為是否升級不應影響字串的行為。
在 eval
中啟用的來源篩選器會外洩到目前正在編譯的任何檔案範圍。以下使用 CPAN 模組 Semi::Semicolons 舉例說明
BEGIN { eval "use Semi::Semicolons; # not filtered" }
# filtered here!
evalbytes
修復了這個問題,讓它能以預期的方式運作
use feature "evalbytes";
BEGIN { evalbytes "use Semi::Semicolons; # filtered" }
# not filtered
如果字串擴充包含浮點數的純量,可能會產生問題。該純量可以擴充為字母,例如 "NaN"
或 "Infinity"
;或者,在 use locale
的範圍內,小數點字元可能是點以外的其他字元(例如逗號)。這些都不太可能以您預期的方式進行剖析。
您應該特別小心記住什麼時候在看什麼
eval $x; # CASE 1
eval "$x"; # CASE 2
eval '$x'; # CASE 3
eval { $x }; # CASE 4
eval "\$$x++"; # CASE 5
$$x++; # CASE 6
上述案例 1 和 2 行為相同:它們執行變數 $x 中包含的程式碼。(儘管案例 2 有誤導性的雙引號,讓讀者懷疑可能發生其他情況 (並未發生任何情況)。) 案例 3 和 4 也以相同的方式執行:它們執行程式碼 '$x'
,除了傳回 $x 的值之外,什麼都不做。(案例 4 純粹出於視覺原因而較受青睞,但它還具有在編譯時而非執行時編譯的優點。) 案例 5 是您通常想要使用雙引號的地方,但在此特定情況下,您可以像案例 6 一樣,改用符號參照。
在 DB
套件中定義的子常式中執行的 eval ''
看不到通常的周圍詞彙範圍,而是呼叫它的第一個非 DB 程式碼片段的範圍。除非您正在撰寫 Perl 除錯器,否則通常不需要擔心這一點。
如果有的話,最後的分號可以從 EXPR 的值中省略。
如果要執行的程式碼沒有變化,您可以使用 eval-BLOCK 形式來捕捉執行時期錯誤,而不會每次都重新編譯而造成負擔。如果有任何錯誤,它仍會傳回 $@
。範例
# make divide-by-zero nonfatal
eval { $answer = $a / $b; }; warn $@ if $@;
# same thing, but less efficient
eval '$answer = $a / $b'; warn $@ if $@;
# a compile-time error
eval { $answer = }; # WRONG
# a run-time error
eval '$answer ='; # sets $@
如果您想要在載入 XS 模組時捕捉錯誤,二進位介面的某些問題 (例如 Perl 版本傾斜) 即使使用 eval
也可能是致命的,除非設定 $ENV{PERL_DL_NONLAZY}
。請參閱 perlrun。
在函式庫中使用 eval {}
形式作為例外陷阱確實有一些問題。由於 __DIE__
鉤子目前可以說是損壞的狀態,您可能希望不要觸發使用者程式碼可能已安裝的任何 __DIE__
鉤子。您可以使用 local $SIG{__DIE__}
建構來執行此目的,如下例所示
# a private exception trap for divide-by-zero
eval { local $SIG{'__DIE__'}; $answer = $a / $b; };
warn $@ if $@;
這一點特別重要,因為 __DIE__
鉤子可以再次呼叫 die
,這會導致其錯誤訊息改變
# __DIE__ hooks may modify error messages
{
local $SIG{'__DIE__'} =
sub { (my $x = $_[0]) =~ s/foo/bar/g; die $x };
eval { die "foo lives here" };
print $@ if $@; # prints "bar lives here"
}
因為這會促進遠距離動作,這種反直覺的行為可能會在未來的版本中得到修正。
eval BLOCK
不會被視為迴圈,所以迴圈控制陳述式 next
、last
或 redo
不能用來離開或重新啟動區塊。
BLOCK 內的最後分號(如果有)可以省略。