你也許已經為了你的系統寫了很多程式碼,其中有些可能重複使用相同的函式。
你可能已經習慣用複製貼上來解決問題,但是你並不滿意。
你可能已經知道很多 Perl 的模組,可以使用裏面的函式。但是你想要自己建立模組。
但是你不知道要如何建立模組。
模組
package My::Math;
use strict;
use warnings;
use Exporter qw(import);
our @EXPORT_OK = qw(add multiply);
sub add {
my ($x, $y) = @_;
return $x + $y;
}
sub multiply {
my ($x, $y) = @_;
return $x * $y;
}
1;
把這個檔案儲存在 somedir/lib/My/Math.pm (或在 Windows 是 somedir\lib\My\Math.pm).
腳本
#!/usr/bin/perl
use strict;
use warnings;
use My::Math qw(add);
print add(19, 23);
把這個檔案儲存在 somedir/bin/app.pl (或在 Windows 是 somedir\bin\app.pl).
現在執行 perl somedir/bin/app.pl. (或在 Windows 是 perl somedir\bin\app.pl).
它會印出像這樣的錯誤訊息:
Can't locate My/Math.pm in @INC (@INC contains:
...
...
...
BEGIN failed--compilation aborted at somedir/bin/app.pl line 9.
問題在哪裡?
這個腳本程式會使用 use
這個關鍵字來載入模組。尤其是 use My::Math qw(add);
這一行。
這樣會在 @INC
這個變數中的目錄下開始尋找叫作 My 的子目錄,然後在 My 的子目錄中在尋找叫作 Math.pm的檔案。
所以這個問題在於你的 .pm 檔案沒有在 Perl 的標準目錄裡:也就是它不在 @INC 所包含的目錄中。
你可以移動你的模組或是可以變更 @INC。
移動模組會造成問題,尤其是必須區分管理者和使用者權限的時候。比如在 Unix 和 Linux 系統中,只有 "root" 更改這些目錄寫入權限。 所以一般我們比較容易的方法是變更 @INC。
從命令列變更 @INC
在我們載入模組之前,我們必須確定模組的目錄有位在 @INC 變數中。
試試看這個:
perl -Isomedir/lib/ somedir/bin/app.pl.
這樣會印出答案: 42。
在這個方法中,-I
標記增加目錄路徑到 @INC 中。
在程式中更改 @INC
因為我們知道 "My" 這個目錄中相對我們的程式,裏面有我們想要執行的模組,所以我們可以改變一下我們的程式:
#!/usr/bin/perl
use strict;
use warnings;
use File::Basename qw(dirname);
use Cwd qw(abs_path);
use lib dirname(dirname abs_path $0) . '/lib';
use My::Math qw(add);
print add(19, 23);
再執行一次這個程式:
perl somedir/bin/app.pl.
成功了!
我們來解釋一下發生什麼事情:
如果在相對目錄中更改 @INC
這一行
use lib dirname(dirname abs_path $0) . '/lib';
增加相對 lib 這個目錄到 @INC
的開頭。
$0
指的是現在這個程式的名子。
abs_path()
of Cwd
傳回這個程式的絕對路徑。
dirname()
of File::Basename
會回傳一個檔案或是目錄的目錄,除了最後一個部份。
在我們的程式中,$0 指的是 app.pl
abs_path($0) 回傳 .../somedir/bin/app.pl
dirname(abs_path $0) 回傳 .../somedir/bin
dirname( dirname abs_path $0) 回傳 .../somedir
這就是我們專案的根目錄。
dirname( dirname abs_path $0) . '/lib' 就是 .../somedir/lib
所以現在我們可以開始使用了
use lib '.../somedir/lib';
而且不需要把真正的目錄給寫死在程式碼中。
上面這一行程式的意思是把 '.../somedir/lib' 加到 @INC 的開頭。
一旦這樣,接下來的程式 use My::Math qw(add);
就會在 '.../somedir/lib' 中發現 "My" 目錄,然後在 '.../somedir/lib/My' 中發現 Math.pm 這個檔案。
這個方法的好處是可以不用在命令列中記得 -I 這個標記。
還有其他方法可以 更改 @INC。
來解釋 use
就如同前面所說的, use
會去尋找 My 目錄以及 Math.pm 檔案。
找到後會把它載入到記憶體中,然後會 import
在模組後參數的函式。在這個範例中 import( qw(add) )
跟 import( 'add' )
是一樣的。
解釋這個程式腳本
其實已經沒有啥好解釋的了。在 use
之後呼叫 import
函式,我們只載入在 My::Math 模組中的
add 函式。這樣就如同在程式宣告這個函式一樣。
比較有趣的是來看看模組的部份。
解釋模組
Perl module 就是檔案加上命名空間(namespace)。package
這個關鍵字建立命名空間。 My::Math 這個模組
就是對應到 My/Math.pm 這個檔案。 A::B::C 這個模組就是對應到 @INC 所包含目錄中的 A/B/C.pm 這個檔案。
回憶一下,use My::Math qw(add);
會載入這個模組然後呼叫 import
函式。 大部份我們不需要去實現自己的 import 函式,所以要去載入
Exporter
這個模組,它就會載入 'import' 函式。
看起來有點令人感到困擾。重要的就是記得: Exporter 可以給你載入的函式。
把要載入的函式放在你模組裏面的 @EXPORT_OK
陣列中。
再解釋清楚一點:模組要 "exports" 函式而程式腳本要 "imports" 函式。
最後我要解釋的是在模組中最後的 1;
。 use 這個敘述會執行這個模組,它需要知道是否執行成功,也就是需要有一個真值的敘述。這個真值的敘述可以是任何東西。
有些人是寫 42;
,有些人是寫 "FALSE"
。每個含有字的字串 都被 Perl 認為是真值。
這樣會造成其他人的困擾。甚至有些人是寫詩的名言。
"Famous last words."
這看起來很棒,但是可能一開始會造成別人的困擾。
這個模組有兩個函式。我們決定要 export 這兩個函式。但是我們的程式腳本只使用其中一個函式。
結論
除了我剛才所解釋的,其實要建立 Perl 模組很簡單。當然你在其他文章會學到更多的東西,但是現在開始把你的常用的函式寫成模組吧。
這裡還有一個關於呼叫模組的建議:
模組命名
建議把模組的第一個字母用大寫表示,其他字母用小寫。建議使用命名空間。
如果你在 Abc 公司上班,我會建議所有的模組都用 Abc:: 來當作命名空間。如果在公司裡的專案叫作 Xyz, 那所有相關的模組應該叫作 Abc::Xyz::
所以如果你有個模組來處理配置檔案,這可以叫作 package Abc::Xyz::Config 這樣等同於 .../projectdir/lib/Abc/Xyz/Config.pm。
請避免你的配置檔案叫作 Config.pm,這樣會讓 Perl (Perl 有自己的 Config.pm) 和你自己感到困擾。