eval EXPR
eval BLOCK
eval

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 在語法錯誤中以清單內容傳回,但不會在執行時期錯誤中傳回。)如果沒有錯誤,$@ 會設定為空字串。像 lastgoto 等控制流程運算子可以略過 $@ 的設定。請注意,使用 eval 既不會讓 Perl 停止將警告列印到 STDERR,也不會將警告訊息的文字填入 $@。要執行這兩個動作,您必須使用 $SIG{__WARN__} 工具,或使用 no warnings 'all' 在 BLOCK 或 EXPR 中關閉警告。請參閱 warnperlvarwarnings

請注意,因為 eval 會捕捉其他會造成致命錯誤的錯誤,因此它對於判斷特定功能(例如 socketsymlink)是否已實作很有用。它也是 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 utf8no 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

如果要執行的程式碼沒有變化,您可以使用 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 不會被視為迴圈,所以迴圈控制陳述式 nextlastredo 不能用來離開或重新啟動區塊。

BLOCK 內的最後分號(如果有)可以省略。