[PERL] 18-套件及模組

URL Link //n.sfs.tw/11980

2017-10-27 10:11:39 By 張○○

PERL 有許多內建或現成的套件(稱為package)可以使用。其實當我們開始撰寫程式的時候,PERL預設就是把我們放在 main這個套件中。

而此套件中的元素,例如變數、陣列、副程式等稱為「符號表」(symbol table),都是存在一個名為 %main 的雜湊中,這個 main 套件是程式的進入點,其他的套件都可以透過 main套件進行存取,只是我們預設都把它給忽略掉,來看以下範例:

#純量變數
$math= 40;
print $main::math; # 40

#副程式
sub power {
  $x = shift;
  $y = shift;
  return $x ** $y;
};

print &main::power(2,4);

#雜湊
%s = ('name'=>'JKS', 'no'=>353);
print $main::s{'name'};

#參照
$ref = sub {
  $x = shift;
  $y = shift;
  return $x ** $y;
};

print main::&$ref(2,4);

注意上面的寫法,由上面的範例可以發現,其實我們都是在 main這個套件中操作。PERL使用兩個冒號 '::' 來取出套件中的內容。

你可以印出這個變數來檢視目前的套件

print __PACKAGE__;

套件的寫法

package 套件名稱;

  套件的內容

1; #套件的結束

注意套件的結束邊界符號並不是大刮號,而是一個數字1和分號;,這個1你也能換成其他的數字。

如果把套件存成一個同名的檔案加上附檔名(.pm),就會變成PERL的模組(perl moudle)。

 

模組的叫用

套件的叫用有兩個方法,一個使用保留字 use,另一個使用保留字 require,這兩者的差異在於 use是在編譯時期就會載入,而require 是在執行期才會載入。所以如果有錯的話  use 在執行前就會出錯而停止,require 得執行到才會報錯。

use 模組名;
use 模組目錄::模組名;
use 模組目錄::模組目錄::模組名;

在叫用套件時,PERL會先去查找 @INC這個廣域變數中設定的目錄,看看你要使用的模組有沒有在,你可以印出@INC來查看,例如我們常會用的 strict 模組,就存在

  /usr/share/perl5/strict.pm

其中目錄/usr/share/perl5/已定義在 @INC之中。

如果你要用的模組不存在,就會出現這樣的錯誤(目錄會因系統不同而不同):

Can't locate XXX.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .)

有時會看到套件名有一個空白,後面跟一個陣列,這是使用該套件的清單(LIST),清單是指裡面的副程式或是變數等等。這裡有一點點小小的複雜,我下面會用簡單的範例來說明。

use 模組名 清單;
use 模組目錄::模組名 清單;

清單是指這次是要使用套件的「預設符號表」或是「需要用到的符號表」或是「定義好的符號表群組」

  * 符號表:套件中的元素,例如變數、陣列、副程式等。

如果不指定,就是使用「預設符號表」,這裡要說明有點複雜,直接用下面的範例來說明。

 

一個自寫套件的範例

現在來撰寫一個自寫的套件,我把這些檔案放在同一個位置,由於@INC本身也包含「目前的目錄」所以可以很順利的找到我們自寫的套件。

例如下面的模組 'ip4.pm' 副程式 ip2bin 能將IP換成二進位的字串:

ip4.pm

package ip4;
use Exporter;

our @ISA= qw(Exporter);
our @EXPORT= qw(ip2bin);
our @EXPORT_OK =qw (dec2bin);
our %EXPORT_TAGS = ( all => [qw(ip2bin dec2bin)]);
our $VERSION = 0.01;

sub ip2bin {
  local $ip=shift;
  local @seg = split('\.',$ip);
  local $ipbin ="";
  foreach(@seg){
      $ipbin .= &dec2bin($_);
  }
  return $ipbin;
}

sub dec2bin {
    my $str = unpack("B32", pack("N", shift));
    return  sprintf("%08d",$str);
}

1;

再另外寫一個程式 03.pl 去叫用我自己撰寫的套件 ip4.pm

03.pl

use ip4;
print &ip2bin('10.20.40.80');

執行結果

00001010000101000010100001010000

說明 ip4.pm

第1行 定義套件名稱,這名稱要和檔名一致
第2行 套件中使用Exporter模組,這個模組讓我們能定義符號表中的私有和公有項目,類似其他語言的 public/private 物件。
第4行 繼承 @ISA 陣列中的套件,@ISA是'is a'的結合,其中列舉的是要繼承的套件
第5行 @EXPORT 陣列列舉出可供叫用的「預設符號表」
第6行 @EXPORT_OK 陣列列舉出可供叫用的「需要用到的符號表」,你必須在清單中指定才能叫用,本文下面會再說明。
第7行 @EXPORT_TAGS 雜湊列舉一些可叫用的組合,例如設定一個全部叫用'all',指定全部的副程式。
第8行 @VERSION 版本,在叫用中也可指定。

第10~18行 把ip變二進位的副程式
第20~23行 把十進位變二進位的副程式

說明  03.pl

第1行 使用套件ip4
第2行 使用套件中的副程式 ip2bin

 

存取權限的問題

在上例 03.pl中,如果想叫用套件 ip4.pm中的另一個副程式 dec2bin會發生什麼事?

print &dec2bin("222");

結果出現錯誤

Undefined subroutine &main::dec2bin called at 03.pl line X.

報錯說找不到此副程式?這就是PERL的存取權限控制。由於在叫用時並沒有指定清單,因此預設只能使用這個套件中 @EXPORT中的符號表。

要解決這個問題,就得在叫用時把清單列進去:

use ip4 qw(dec2bin);

因為 dec2bin 有寫在 @EXPORT_OK 陣列中,所以可以列進去,但不幸的原來的副程式 ip2bin 就不能用。只好一併列進去:

use ip4 qw(ip2bin dec2bin);

如果一個套件裡面有數十個符號表項目,這樣列會死人的,還好我們在 EXPORT_TAGS有定義一個群組'all',可以直接叫用(在前面加上冒號)

use ip4 qw(:all);

這樣就解決我們的問題。

套件和模組簡單的寫到這裡,讓各位有基本認識即可。事實上,會自己寫模組的機會少,用別人的模組機會多,下一個單元會寫模組的安裝和維護。

 

上一篇 17-參照
回到目錄 01-撰寫第一隻PERL程式
下一篇 19-模組的安裝和維護


後記:這篇真的很難寫,我一直覺得這篇是不是太難了不適合寫在這系列中,不過我還是用最簡單的認知下把他寫完了,所以篇幅有點長。