內容

名稱

perlfaq7 - Perl 語言一般議題

版本

版本 5.20210520

說明

本節探討 Perl 語言的一般議題,這些議題無法明確歸類到其他任何節。

我可以取得 Perl 語言的 BNF/yacc/RE 嗎?

沒有 BNF,但如果你特別勇敢,你可以從原始程式碼散佈中的 perly.y 逐行查看 yacc 語法。語法仰賴極為聰明的標記化程式碼,所以也要準備深入 toke.c。

用 Chaim Frenkel 的話來說:「Perl 的語法無法簡化為 BNF。解析 perl 的工作分佈在 yacc、詞法分析器、障眼法和鏡子之間。」

所有這些 $@%&* 標點符號是什麼,我如何知道何時使用它們?

它們是型別指定符號,詳情請參閱 perldata

$ for scalar values (number, string or reference)
@ for arrays
% for hashes (associative arrays)
& for subroutines (aka functions, procedures, methods)
* for all types of that symbol name. In version 4 you used them like
  pointers, but in modern perls you can just use references.

你可能會遇到一些其他符號,它們並非真正的型別指定符號

<> are used for inputting a record from a filehandle.
\  takes a reference to something.

請注意 <FILE> 既不是檔案的型別指定符號,也不是句柄的名稱。它是應用於句柄 FILE 的 <> 算子。它從句柄 FILE 讀取一行(好吧,記錄 - 請參閱 "$/" in perlvar),在純量環境中,或在列表環境中讀取所有行。在對檔案執行開啟、關閉或 <> 以外的任何其他操作,甚至在討論句柄時,請勿使用括號。以下是正確的:eof(FH)seek(FH, 0, 2) 和「從 STDIN 複製到 FILE」。

我必須永遠/永遠不引用我的字串或使用分號和逗號嗎?

通常,裸字不需要引用,但在大多數情況下可能應該引用(並且必須在 use strict 下引用)。但包含一個簡單字詞的雜湊鍵和 => 算子左邊運算元都算作已引用

This                    is like this
------------            ---------------
$foo{line}              $foo{'line'}
bar => stuff            'bar' => stuff

區塊中的最後分號和清單中的最後逗號都是可選的。良好的風格(請參閱 perlstyle)建議除了單行之外,都要加上它們

if ($whoops) { exit 1 }
my @nums = (1, 2, 3);

if ($whoops) {
    exit 1;
}

my @lines = (
    "There Beren came from mountains cold",
    "And lost he wandered under leaves",
);

如何略過一些回傳值?

一種方法是將回傳值視為清單並索引它

$dir = (getpwnam($user))[7];

另一種方法是在左側使用 undef 作為元素

($dev, $ino, undef, undef, $uid, $gid) = stat($file);

您也可以使用清單切片來選取僅需要的元素

($dev, $ino, $uid, $gid) = ( stat($file) )[0,1,4,5];

如何暫時封鎖警告?

如果您執行 Perl 5.6.0 或更高版本,use warnings 實用程式允許精細控制產生的警告。請參閱 perllexwarn 以取得更多詳細資訊。

{
    no warnings;          # temporarily turn off warnings
    $x = $y + $z;         # I know these might be undef
}

此外,您可以啟用和停用警告類別。您可以關閉要忽略的類別,並且仍然可以取得其他類別的警告。請參閱 perllexwarn 以取得完整詳細資訊,包括類別名稱和層級結構。

{
    no warnings 'uninitialized';
    $x = $y + $z;
}

如果您有較舊版本的 Perl,$^W 變數(在 perlvar 中說明)會控制區塊的執行時期警告

{
    local $^W = 0;        # temporarily turn off warnings
    $x = $y + $z;         # I know these might be undef
}

請注意,與所有標點符號變數一樣,您目前無法對 $^W 使用 my(),只能使用 local()。

什麼是擴充?

擴充是一種從 Perl 呼叫已編譯 C 程式碼的方法。閱讀 perlxstut 是深入了解擴充的絕佳方法。

為什麼 Perl 運算子的優先順序與 C 運算子不同?

實際上,它們沒有。Perl 複製的所有 C 運算子在 Perl 中的優先順序與在 C 中相同。問題在於 C 沒有的運算子,特別是將清單內容給予其右側所有內容的函式,例如 print、chmod、exec 等。此類函式稱為「清單運算子」,並顯示在 perlop 中的優先順序表中。

常見的錯誤是寫成

unlink $file || die "snafu";

這會被解釋為

unlink ($file || die "snafu");

若要避免此問題,請加上額外的括號或使用優先順序極低的 or 運算子

(unlink $file) || die "snafu";
unlink $file or die "snafu";

「英語」運算子(andorxornot)的優先順序故意低於清單運算子,就是為了應付上述情況。

另一個具有驚人優先順序的運算子是乘方。它比一元減號的結合力更強,使 -2**2 產生負四而不是正四。它也是右結合的,表示 2**3**2 是二的九次方,而不是八的平方。

雖然與 C 中的優先順序相同,Perl 的 ?: 運算子會產生一個左值。這會將 $x 指定給 $if_true 或 $if_false,具體取決於 $maybe 的真假。

($maybe ? $if_true : $if_false) = $x;

如何宣告/建立結構?

一般來說,您不會「宣告」結構。只需使用(可能是匿名的)雜湊參照。有關詳細資訊,請參閱 perlrefperldsc。以下是範例

$person = {};                   # new anonymous hash
$person->{AGE}  = 24;           # set field AGE to 24
$person->{NAME} = "Nat";        # set field NAME to "Nat"

如果您正在尋找更嚴謹的東西,請嘗試 perlootut

如何建立模組?

perlnewmod 是個不錯的起點,如果您不想公開您的模組,請忽略有關上傳到 CPAN 的部分。

ExtUtils::ModuleMakerModule::Starter 也是不錯的起點。許多 CPAN 作者現在使用 Dist::Zilla 盡可能自動化。

可以在以下位置找到有關模組的詳細文件:perlmodperlmodlibperlmodstyle

如果您需要包含 C 程式碼或 C 函式庫介面,請使用 h2xs。h2xs 將建立模組分發結構和初始介面檔案。 perlxsperlxstut 會說明詳細資訊。

如何採用或接手 CPAN 上已存在的模組?

請目前的維護者讓您成為共同維護者或將模組轉移給您。

如果您因為某些原因無法聯絡作者,請聯絡 modules@perl.org 的 PAUSE 管理員,他們可能會提供協助,但每個案例會個別處理。

如何建立類別?

(由 brian d foy 貢獻)

在 Perl 中,類別只是一個套件,而方法只是一個子常式。Perl 沒有比這更正式的說法,並讓您按照自己的喜好設定套件(也就是說,它不會為您設定任何東西)。

另請參閱 perlootut,這是一個涵蓋類別建立的教學課程,以及 perlobj

如何判斷變數是否受到污染?

您可以使用 Scalar::Util 模組的 tainted() 函式,可從 CPAN 取得(或從 Perl 5.8.0 版開始包含在內)。另請參閱 "perlsec 中的「洗滌和偵測已污染的資料」

什麼是閉包?

閉包記載於 perlref 中。

閉包 是電腦科學中一個精確但難以解釋的術語。通常,閉包在 Perl 中實作為匿名子常式,並持續參照其範圍之外的詞彙變數。這些詞彙會神奇地參照子常式定義時存在的變數(深度繫結)。

閉包最常使用於您可以讓函式的回傳值本身就是一個函式的程式語言中,就像您可以在 Perl 中一樣。請注意,有些語言提供匿名函式,但無法提供適當的閉包:例如 Python 語言。如需有關閉包的更多資訊,請查看任何一本函式程式設計教科書。Scheme 是一種不僅支援而且鼓勵閉包的語言。

以下是經典的非閉包函式產生函式

sub add_function_generator {
    return sub { shift() + shift() };
}

my $add_sub = add_function_generator();
my $sum = $add_sub->(4,5);                # $sum is 9 now.

add_function_generator() 回傳的匿名子常式在技術上不是閉包,因為它不參照其範圍之外的任何詞彙。使用閉包會提供一個函式範本,其中留有一些自訂槽位,以便稍後填入。

將其與以下 make_adder() 函式進行對比,其中回傳的匿名函式包含對該函式範圍之外的詞彙變數的參照。此類參照要求 Perl 回傳適當的閉包,從而永久鎖定函式建立時詞彙所具有的值。

sub make_adder {
    my $addpiece = shift;
    return sub { shift() + $addpiece };
}

my $f1 = make_adder(20);
my $f2 = make_adder(555);

現在 $f1->($n) 永遠是 20 加上您傳入的任何 $n,而 $f2->($n) 永遠是 555 加上您傳入的任何 $n。閉包中的 $addpiece 會持續存在。

閉包通常用於較不深奧的目的。例如,當您想要將一段程式碼傳遞到函式中時

my $line;
timeout( 30, sub { $line = <STDIN> } );

如果要執行的程式碼已作為字串傳入,'$line = <STDIN>',則假設的 timeout() 函式將無法存取呼叫者範圍中的詞彙變數 $line。

閉包的另一個用途是讓變數對指定的子常式私有,例如在子常式建立時初始化的計數器,並且只能從子常式內部修改。這有時會與套件檔案中的 BEGIN 區塊一起使用,以確保變數在套件的生命週期內不會受到干擾

BEGIN {
    my $id = 0;
    sub next_id { ++$id }
}

perlsub 中有更詳細的討論;請參閱持續性私人變數的條目。

什麼是變數自殺,我如何防止它?

這個問題已在 perl 5.004_05 中修復,因此防止它的方法是升級您的 perl 版本。 ;)

變數自殺是指您(暫時或永久)遺失變數的值。它是由 my() 和 local() 與封閉或別名 foreach() 迭代器變數和子常式引數互動作用的範圍所造成的。以前很容易無意間以這種方式遺失變數的值,但現在難度高多了。採用以下程式碼

my $f = 'foo';
sub T {
    while ($i++ < 3) { my $f = $f; $f .= "bar"; print $f, "\n" }
}

T;
print "Finally $f\n";

如果您遇到變數自殺,子常式中的 my $f 就不會擷取值為 'foo'$f 的新副本。輸出顯示在子常式中,$f 的值在不應該洩漏時洩漏,如下面的輸出所示

foobar
foobarbar
foobarbarbar
Finally foo

將 "bar" 新增三次的 $f 應該是新的 $f my $f 應該在迴圈每次執行時建立新的詞彙變數。預期的輸出為

foobar
foobar
foobar
Finally foo

如何傳遞/傳回 {函式、檔案控制代碼、陣列、雜湊、方法、正規表示式}?

您需要傳遞這些物件的參照。針對這個特定問題,請參閱 perlsub 中的「傳遞參照」,並參閱 perlref 以取得有關參照的資訊。

傳遞變數和函式

常規變數和函式很容易傳遞:只要傳遞參照至現有的或匿名的變數或函式即可

func( \$some_scalar );

func( \@some_array  );
func( [ 1 .. 10 ]   );

func( \%some_hash   );
func( { this => 10, that => 20 }   );

func( \&some_func   );
func( sub { $_[0] ** $_[1] }   );
傳遞檔案控制代碼

從 Perl 5.6 開始,您可以使用標量變數表示檔案控制代碼,並將其視為任何其他標量。

open my $fh, $filename or die "Cannot open $filename! $!";
func( $fh );

sub func {
    my $passed_fh = shift;

    my $line = <$passed_fh>;
}

在 Perl 5.6 之前,您必須使用 *FH\*FH 符號。這些是「類型全域變數」—請參閱 perldata 中的「類型全域變數和檔案控制代碼」,特別是 perlsub 中的「傳遞參照」 以取得更多資訊。

傳遞正規表示式

以下是如何傳遞字串和正規表示式以供其比對的範例。您可以使用 qr// 運算子建構模式

sub compare {
    my ($val1, $regex) = @_;
    my $retval = $val1 =~ /$regex/;
    return $retval;
}
$match = compare("old McDonald", qr/d.*D/i);
傳遞方法

要將物件方法傳入子常式中,您可以執行下列動作

call_a_lot(10, $some_obj, "methname")
sub call_a_lot {
    my ($count, $widget, $trick) = @_;
    for (my $i = 0; $i < $count; $i++) {
        $widget->$trick();
    }
}

或者,您可以使用封閉函數將物件、其方法呼叫和引數綑綁在一起

my $whatnot = sub { $some_obj->obfuscate(@args) };
func($whatnot);
sub func {
    my $code = shift;
    &$code();
}

您也可以調查 UNIVERSAL 類別中的 can() 方法(標準 perl 發行版的一部分)。

如何建立靜態變數?

(由 brian d foy 貢獻)

在 Perl 5.10 中,使用 state 宣告變數。state 宣告會建立在子常式呼叫之間持續存在的詞彙變數

sub counter { state $count = 1; $count++ }

您可以透過使用超出範圍的詞彙變數來偽造靜態變數。在此範例中,您定義子常式 counter,它使用詞彙變數 $count。由於您將其包在 BEGIN 區塊中,因此 $count 會在編譯時定義,但也會在 BEGIN 區塊結束時超出範圍。BEGIN 區塊也會確保子常式及其使用的值會在編譯時定義,因此子常式可以像任何其他子常式一樣準備好使用,而且您可以將此程式碼放在程式文字中與其他子常式相同的地方(亦即通常在程式碼的結尾)。子常式 counter 仍然有資料的參考,而且是您可以存取該值(而且每次執行時都會增加該值)的唯一方式。由 $count 定義的記憶體區塊中的資料是 counter 的私有資料。

BEGIN {
    my $count = 1;
    sub counter { $count++ }
}

my $start = counter();

.... # code that calls counter();

my $end = counter();

在先前的範例中,您建立了函數私有變數,因為只有一個函數記住了它的參考。您可以在變數在範圍內時定義多個函數,而且每個函數都可以共用「私有」變數。它並非真正的「靜態」,因為您可以在詞彙變數在範圍內時在函數外部存取它,甚至建立它的參考。在此範例中,increment_countreturn_count 共用變數。一個函數會增加該值,而另一個函數只會傳回該值。它們都可以存取 $count,而且由於它已經超出範圍,因此沒有其他方式可以存取它。

BEGIN {
    my $count = 1;
    sub increment_count { $count++ }
    sub return_count    { $count }
}

要宣告檔案私有變數,您仍然使用詞彙變數。檔案也是一個範圍,因此在檔案中定義的詞彙變數無法從任何其他檔案看到。

請參閱 "perlsub 中的「持續性私有變數」 以取得更多資訊。即使我們在此解答中沒有使用匿名子常式,perlref 中對封閉函數的討論可能對您有幫助。請參閱 "perlsub 中的「持續性私有變數」 以取得詳細資料。

動態和詞彙(靜態)範圍之間的差異是什麼?local() 和 my() 之間的差異是什麼?

local($x) 會儲存全域變數 $x 的舊值,並在子常式持續時間內指定一個新值,在從該子常式呼叫的其他函式中可見。這是在執行時完成的,因此稱為動態範圍。local() 始終會影響全域變數,也稱為套件變數或動態變數。

my($x) 會建立一個新變數,僅在目前子常式中可見。這是在編譯時完成的,因此稱為詞彙或靜態範圍。my() 始終會影響私有變數,也稱為詞彙變數或(不正確地)靜態(範圍)變數。

例如

sub visible {
    print "var has value $var\n";
}

sub dynamic {
    local $var = 'local';    # new temporary value for the still-global
    visible();              #   variable called $var
}

sub lexical {
    my $var = 'private';    # new private variable, $var
    visible();              # (invisible outside of sub scope)
}

$var = 'global';

visible();              # prints global
dynamic();              # prints local
lexical();              # prints global

請注意,在任何時候都未列印「private」值。這是因為 $var 僅在 lexical() 函式的區塊內具有該值,而且它對呼叫的子常式隱藏。

總之,local() 並不會讓您想到私有、區域變數。它會給全域變數一個暫時值。如果您想要私有變數,my() 是您要尋找的。

請參閱 perlsub 中的 "透過 my() 的私有變數""透過 local() 的暫時值" 以取得詳盡的詳細資料。

當類似命名的詞彙在範圍內時,我如何存取動態變數?

如果您知道您的套件,您可以明確地提到它,例如 $Some_Pack::var。請注意,符號 $::var 不是目前套件中的動態 $var,而是「main」套件中的動態 $var,就好像您寫了 $main::var 一樣。

use vars '$var';
local $var = "global";
my    $var = "lexical";

print "lexical is $var\n";
print "global  is $main::var\n";

或者,您可以使用編譯指令 our() 將動態變數帶入目前詞彙範圍。

require 5.006; # our() did not exist before 5.6
use vars '$var';

local $var = "global";
my $var    = "lexical";

print "lexical is $var\n";

{
    our $var;
    print "global  is $var\n";
}

深層繫結和淺層繫結之間的差異是什麼?

在深層繫結中,匿名子常式中提到的詞彙變數與建立子常式時在範圍內的變數相同。在淺層繫結中,它們是當呼叫子常式時碰巧在範圍內具有相同名稱的變數。Perl 始終使用詞彙變數的深層繫結(即,使用 my() 建立的變數)。但是,動態變數(又稱全域、區域或套件變數)實際上是淺層繫結的。請將這視為另一個不使用它們的理由。請參閱 "什麼是封閉?" 的答案。

為什麼「my($foo) = <$fh>;」無法正常運作?

my()local() 會將清單內容給予 = 右側。<$fh> 讀取操作,就像 Perl 的許多函式和運算子一樣,可以判斷它是在哪個內容中被呼叫,並做出適當的行為。一般來說,scalar() 函式可以提供協助。此函式本身不會對資料做任何事(與普遍的迷思相反),而是告訴它的參數以任何純量的方式運作。如果該函式沒有定義純量行為,這當然幫不了你(例如 sort())。

不過,要在此特定情況中強制執行純量內容,你只需要省略括號即可

local($foo) = <$fh>;        # WRONG
local($foo) = scalar(<$fh>);   # ok
local $foo  = <$fh>;        # right

你應該還是使用詞彙變數,儘管這裡的問題是一樣的

my($foo) = <$fh>;    # WRONG
my $foo  = <$fh>;    # right

如何重新定義內建函式、運算子或方法?

你為什麼想這麼做? :-)

如果你想覆寫預先定義的函式,例如 open(),則必須從不同的模組匯入新的定義。請參閱 "perlsub 中的「覆寫內建函式」

如果你想重載 Perl 運算子,例如 +**,則需要使用 use overload 實用範例,記載於 overload 中。

如果你在討論模糊化父類別中的方法呼叫,請參閱 "perlootut 中的「覆寫方法和方法解析」

&foofoo() 呼叫函式的差別是什麼?

(由 brian d foy 貢獻)

&foo 呼叫子常式,且沒有尾隨括號,會忽略 foo 的原型,並傳遞參數清單 @_ 的目前值給它。以下是一個範例;bar 子常式呼叫 &foo,它會列印其參數清單

sub foo { print "Args in foo are: @_\n"; }

sub bar { &foo; }

bar( "a", "b", "c" );

當你以參數呼叫 bar 時,你會看到 foo 取得相同的 @_

Args in foo are: a b c

以尾隨括號呼叫子常式,無論有沒有參數,都不會使用目前的 @_。將範例變更為在呼叫 foo 後加上括號,會變更程式

sub foo { print "Args in foo are: @_\n"; }

sub bar { &foo(); }

bar( "a", "b", "c" );

現在輸出顯示 foo 沒有從呼叫者取得 @_

Args in foo are:

不過,在呼叫中使用 & 仍會覆寫 foo 的原型(如果存在)

sub foo ($$$) { print "Args infoo are: @_\n"; }

sub bar_1 { &foo; }
sub bar_2 { &foo(); }
sub bar_3 { foo( $_[0], $_[1], $_[2] ); }
# sub bar_4 { foo(); }
# bar_4 doesn't compile: "Not enough arguments for main::foo at ..."

bar_1( "a", "b", "c" );
# Args in foo are: a b c

bar_2( "a", "b", "c" );
# Args in foo are:

bar_3( "a", "b", "c" );
# Args in foo are: a b c

@_ 傳遞功能的主要用途是撰寫子常式,其主要工作是為你呼叫其他子常式。如需進一步詳細資訊,請參閱 perlsub

如何建立 switch 或 case 陳述式?

Perl 中有一個 given/when 語句,但它是實驗性的,未來可能會改變。有關更多詳細資訊,請參閱 perlsyn

一般的答案是使用 CPAN 模組,例如 Switch::Plain

use Switch::Plain;
sswitch($variable_holding_a_string) {
    case 'first': { }
    case 'second': { }
    default: { }
}

或對於更複雜的比較,if-elsif-else

for ($variable_to_test) {
    if    (/pat1/)  { }     # do something
    elsif (/pat2/)  { }     # do something else
    elsif (/pat3/)  { }     # do something else
    else            { }     # default
}

以下是一個基於模式比對的 switch 簡單範例,排列方式讓它看起來更像 switch 語句。我們將根據儲存在 $whatchamacallit 中的參照類型來執行多重條件。

SWITCH: for (ref $whatchamacallit) {

    /^$/           && die "not a reference";

    /SCALAR/       && do {
                    print_scalar($$ref);
                    last SWITCH;
                  };

    /ARRAY/        && do {
                    print_array(@$ref);
                    last SWITCH;
                  };

    /HASH/        && do {
                    print_hash(%$ref);
                    last SWITCH;
                  };

    /CODE/        && do {
                    warn "can't print function ref";
                    last SWITCH;
                  };

    # DEFAULT

    warn "User defined type skipped";

}

請參閱 perlsyn 以取得此樣式的其他範例。

有時您應該變更常數和變數的位置。例如,假設您想測試已給予的許多答案中的哪一個,但以不區分大小寫的方式進行,同時也允許縮寫。如果您所有的字串都以不同的字元開頭,或者您想安排比對,以便一個優先於另一個,例如這裡的 "SEND" 優先於 "STOP",則可以使用下列技巧

chomp($answer = <>);
if    ("SEND"  =~ /^\Q$answer/i) { print "Action is send\n"  }
elsif ("STOP"  =~ /^\Q$answer/i) { print "Action is stop\n"  }
elsif ("ABORT" =~ /^\Q$answer/i) { print "Action is abort\n" }
elsif ("LIST"  =~ /^\Q$answer/i) { print "Action is list\n"  }
elsif ("EDIT"  =~ /^\Q$answer/i) { print "Action is edit\n"  }

完全不同的方法是建立函式參照的雜湊。

my %commands = (
    "happy" => \&joy,
    "sad",  => \&sullen,
    "done"  => sub { die "See ya!" },
    "mad"   => \&angry,
);

print "How are you? ";
chomp($string = <STDIN>);
if ($commands{$string}) {
    $commands{$string}->();
} else {
    print "No such command: $string\n";
}

從 Perl 5.8 開始,來源篩選模組 Switch 也可用於取得 switch 和 case。現在不建議使用它,因為它與 Perl 5.10 的原生 switch 不完全相容,而且由於它是作為來源篩選器實作的,因此在涉及複雜語法時,它並不總是能按預期運作。

如何捕捉對未定義變數、函式或方法的存取?

"perlsub 中的自動載入" 中討論的 AUTOLOAD 方法,讓您可以擷取對未定義函式和方法的呼叫。

當遇到在 use warnings 下會觸發警告的未定義變數時,您可以將警告提升為錯誤。

use warnings FATAL => qw(uninitialized);

為什麼找不到包含在此檔案中的方法?

一些可能的原因:您的繼承混淆了、您拼錯了方法名稱,或物件類型錯誤。請查看 perlootut 以取得上述任何情況的詳細資訊。您也可以使用 print ref($object) 來找出 $object 被祝福成哪個類別。

問題的另一個可能原因是您在 Perl 看到此類套件存在之前,對類別名稱使用了間接物件語法(例如,find Guru "Samy")。最好確保在開始使用套件之前,所有套件都已定義,如果您使用 use 語句而不是 require,這將會得到處理。如果不是,請務必改用箭頭符號(例如,Guru->find("Samy"))。perlobj 中說明了物件符號。

請務必閱讀 perlmod 中關於建立模組的資訊,以及 "perlobj 中的方法呼叫" 中關於間接物件的危險性。

如何找出我目前的或呼叫套件?

(由 brian d foy 貢獻)

若要找出你目前所在的套件,請使用特殊字面值 __PACKAGE__,如 perldata 所述。你只能將特殊字面值當成個別的標記使用,因此你無法像使用變數那樣將它們內插到字串中

my $current_package = __PACKAGE__;
print "I am in package $current_package\n";

如果你想找出呼叫你的程式碼的套件,可能是為了提供更好的診斷,就像 Carp 所做的那樣,請使用內建的 caller

sub foo {
    my @args = ...;
    my( $package, $filename, $line ) = caller;

    print "I was called from package $package\n";
    );

預設情況下,你的程式會從套件 main 開始執行,因此你永遠都會在某個套件中。

這與找出物件被祝福進入的套件不同,後者可能不是目前的套件。對於後者,請使用 Scalar::Util 中的 blessed,自 Perl 5.8 起成為標準函式庫的一部分

use Scalar::Util qw(blessed);
my $object_package = blessed( $object );

大多數時候,你不應該關心物件被祝福進入哪個套件,只要它宣稱繼承自該類別即可

my $is_right_class = eval { $object->isa( $package ) }; # true or false

而且,在 Perl 5.10 及更新版本中,你不必檢查繼承關係就能判斷物件是否可以處理角色。對於後者,你可以使用來自 UNIVERSALDOES

my $class_does_it = eval { $object->DOES( $role ) }; # true or false

你可以安全地用 DOES 取代 isa(儘管反之則不然)。

如何註解掉一大段 Perl 程式碼?

(由 brian d foy 貢獻)

註解掉多行 Perl 的快速且簡便的方法是用 Pod 指令將這些行圍起來。你必須將這些指令放在行首,並放在 Perl 預期新陳述式的地方(因此不要放在陳述式中間,例如 # 註解)。你可以使用 =cut 結束註解,結束 Pod 區段

=pod

my $object = NotGonnaHappen->new();

ignored_sub();

$wont_be_assigned = 37;

=cut

快速且簡便的方法只有在你沒有計畫在原始程式碼中保留已註解程式碼時才有效。如果出現 Pod 解析器,你的多行註解將會顯示在 Pod 翻譯中。更好的方法也可以將其隱藏在 Pod 解析器中。

=begin 指令可以標記一個區段以供特定目的使用。如果 Pod 解析器不想處理它,它就會忽略它。使用 comment 標籤註解。使用具有相同標籤的 =end 結束註解。你仍然需要 =cut 從 Pod 註解返回 Perl 程式碼

=begin comment

my $object = NotGonnaHappen->new();

ignored_sub();

$wont_be_assigned = 37;

=end comment

=cut

有關 Pod 的更多資訊,請查看 perlpodperlpodspec

如何清除套件?

使用由 Mark-Jason Dominus 提供的這段程式碼

sub scrub_package {
    no strict 'refs';
    my $pack = shift;
    die "Shouldn't delete main package"
        if $pack eq "" || $pack eq "main";
    my $stash = *{$pack . '::'}{HASH};
    my $name;
    foreach $name (keys %$stash) {
        my $fullname = $pack . '::' . $name;
        # Get rid of everything with that name.
        undef $$fullname;
        undef @$fullname;
        undef %$fullname;
        undef &$fullname;
        undef *$fullname;
    }
}

或者,如果你使用的是 Perl 的近期版本,你可以直接使用 Symbol::delete_package() 函數。

如何將變數用作變數名稱?

初學者常常認為他們希望變數包含變數名稱。

$fred    = 23;
$varname = "fred";
++$$varname;         # $fred now 24

這在有時候可行,但由於兩個原因,這是一個非常糟糕的主意。

第一個原因是此技巧僅適用於全域變數。這表示如果 $fred 是在上述範例中使用 my() 建立的詞彙變數,則程式碼完全無法運作:你會意外存取全域變數,並完全跳過私有詞彙。全域變數很糟糕,因為它們很容易意外發生衝突,而且通常會產生無法擴充且令人困惑的程式碼。

符號參照在 use strict pragma 下是被禁止的。它們不是真正的參照,因此不會被參考計數或垃圾回收。

使用變數來儲存另一個變數名稱的另一個原因是一個糟糕的主意,是因為這個問題通常源自於對 Perl 資料結構,特別是雜湊的理解不足。透過使用符號參照,你只是使用套件的符號表雜湊(例如 %main::),而不是使用者定義的雜湊。解決方案是使用你自己的雜湊或真正的參照。

$USER_VARS{"fred"} = 23;
my $varname = "fred";
$USER_VARS{$varname}++;  # not $$varname++

我們在那裡使用 %USER_VARS 雜湊,而不是符號參照。有時這會出現在使用變數參照從使用者讀取字串,並希望將它們擴充為 Perl 程式變數的值。這也是一個糟糕的主意,因為它混淆了程式可存取的命名空間和使用者可存取的命名空間。與讀取字串並將它擴充為程式自己變數的實際內容相比

$str = 'this has a $fred and $barney in it';
$str =~ s/(\$\w+)/$1/eeg;          # need double eval

最好保留一個雜湊,例如 %USER_VARS,並讓變數參照實際上參照該雜湊中的項目

$str =~ s/\$(\w+)/$USER_VARS{$1}/g;   # no /e here at all

這比之前的做法更快、更乾淨、更安全。當然,你不必使用美元符號。你可以使用自己的方案,讓它不那麼令人困惑,例如括號中的百分比符號等。

$str = 'this has a %fred% and %barney% in it';
$str =~ s/%(\w+)%/$USER_VARS{$1}/g;   # no /e here at all

人們有時認為他們希望變數包含變數名稱的另一個原因是,他們不知道如何使用雜湊建立適當的資料結構。例如,假設他們希望在程式中使用兩個雜湊:%fred 和 %barney,並且他們希望使用另一個標量變數按名稱參照它們。

$name = "fred";
$$name{WIFE} = "wilma";     # set %fred

$name = "barney";
$$name{WIFE} = "betty";    # set %barney

這仍然是一個符號參考,並且仍然存在上面列舉的問題。寫以下內容會好得多

$folks{"fred"}{WIFE}   = "wilma";
$folks{"barney"}{WIFE} = "betty";

並從一開始就使用多層級雜湊。

你絕對必須使用符號參考的唯一時間是當你真的必須參照符號表時。這可能是因為它是一些無法取得實際參考的內容,例如格式名稱。對於方法呼叫,這樣做也很重要,因為這些呼叫總是透過符號表進行解析。

在這些情況下,你可以暫時關閉 strict 'refs',以便你可以使用符號表。例如

@colors = qw(red blue green yellow orange purple violet);
for my $name (@colors) {
    no strict 'refs';  # renege for the block
    *$name = sub { "<FONT COLOR='$name'>@_</FONT>" };
}

所有這些函式(red()、blue()、green() 等)看起來是分開的,但封閉中的實際程式碼實際上只編譯過一次。

因此,有時你可能希望使用符號參考直接操作符號表。對於格式、控制代碼和子常式,這並不重要,因為它們總是全域性的——你無法對它們使用 my()。不過,對於標量、陣列和雜湊——以及通常對於子常式——你可能只希望使用硬參考。

「錯誤的直譯器」是什麼意思?

(由 brian d foy 貢獻)

「錯誤的直譯器」訊息來自殼層程式,而不是 perl。實際訊息可能會因你的平台、殼層程式和區域設定而異。

如果你看到「錯誤的直譯器 - 沒有此類檔案或目錄」,則 perl 程式碼中的第一行(「shebang」行)不包含 perl 的正確路徑(或任何其他能夠執行程式的程式)。有時,當你將程式碼從一台電腦移到另一台電腦時,就會發生這種情況,而且每台電腦都有不同的 perl 路徑——例如 /usr/bin/perl 與 /usr/local/bin/perl。它也可能表示來源電腦有 CRLF 行終結符,而目標電腦只有 LF:殼層程式會嘗試尋找 /usr/bin/perl<CR>,但找不到。

如果你看到「錯誤的直譯器:拒絕存取權限」,則需要讓你的程式碼可執行。

在任何情況下,你仍然應該能夠使用 perl 明確執行腳本

% perl script.pl

如果你收到「perl:找不到指令」之類的訊息,表示 perl 不在你的 PATH 中,這也可能表示 perl 的位置不在你預期的地方,因此你需要調整你的 shebang 行。

當 C 函式庫變更時,我是否需要重新編譯 XS 模組?

(由 Alex Beamish 提供)

如果新版本的 C 函式庫與你正在升級的版本相容 ABI(這是應用程式二進制介面相容),而且共用函式庫版本沒有變更,則不需要重新編譯。

作者和版權

版權所有 (c) 1997-2013 Tom Christiansen、Nathan Torkington,以及其他註明的作者。保留所有權利。

此文件是免費的文件;你可以根據與 Perl 相同的條款重新散布或修改它。

不論其散布方式,此檔案中的所有程式碼範例特此置於公有領域。你被允許且鼓勵在自己的程式中使用此程式碼,無論是為了娛樂或為了獲利,都可以依你所見的方式使用。在程式碼中加上一個簡單的註解表示感謝會是種禮貌,但並非必要。