---perl的常用CPAN---from web

附錄B:常用的五十個CPAN模組

這個附錄的內容原來是來自於Autrijus Tang(唐宗漢)的「CPAN模組數來寶」,正如我們說的,使用Perl而不使用CPAN實在是無法感覺到那種驚人的集結力量。可是CPAN模組數量之多,要能從中得到一個適合自己的模組,也有相當的難度。因此能夠以一般最常使用的模組來作為熟悉CPAN的開始,確實是美事一樁。而這份投影片曾經在許多地方發表過,包括台北的「Perl/PHP/Python Party」、德國的「German Perl Workshop」,以及美國的「Open Source Conference 2003」發表過,也都獲得很好的迴響。所以我就決定把這一份投影片改寫為這本書的附錄。

CPANPLUS

我們前面提過好幾次關於CPANPLUS,因為這個模組很可能在接下來的Perl版本中,將接替CPAN成為預設的模組安裝管理工具。

% cpanp
a # 依作者名稱搜尋模組
m # 依模組名稱搜尋模組
f # 列出作者的所有套件
o # 列出可供更新的模組
i # 安裝
t # 測試
u # 移除
d # 下載
l # 詳細資訊
r # 顯示README
c # 品管報告
z # 解開模組

LWP::Simple

利用LWP::Simple,可以很容易的讓我們在Perl程式中取得某個網頁的內容,這經常被拿來運用於資料的收集。

$page = get("http://www.cpan.org/"); # 取得網頁
getprint($url); # 印出內容
getstore($url, $file); # 存入檔案
head($url) # 取得標頭
mirror($url, $file); # 映射網址

WWW::Mechanize

如果你每天都要進行相同的步驟去登入某個網站,或填入某些資料取得相關的資訊,那麼你可以可以透過WWW::Mechanize來請Perl幫忙。他可以像一個機器人般的,幫你進行這些繁雜的手續,就像你的代理人一般。

my $agent = WWW::Mechanize->new(); # 建立物件
$agent->get($url); # 到達網站
$agent->follow($link); # 按下鏈結
$agent->form($number); # 進入表單
$agent->field($name, $value); # 輸入資料
$agent->click($button); # 按下按鈕
$agent->back(); # 回上一頁
$agent->add_header($name => $value); # 加入標頭
print $agent->{content}; # 印出結果

HTML::Mason

我們在正文中也提過這個模組,他可以讓我們簡單的寫出動態的網站,就像你在寫HTML一樣。如果你熟悉PHP,你應該也會很習慣這個方式,當然,他其實是非常強力的內嵌式模板系統。

<%perl>
my $noun = '全世界'; # 內嵌程式碼
my @time = split /[\s:]/, localtime;

歡迎<% $noun %>, # 安插運算式
% if ( $time[3] < 12 ) { # 流程控制
早安!
% } else {
晚安!
% }

Template

另一個在Perl領域中常用的模板系統,他可以很方便的讓你切開頁面設計跟程式碼,讓兩者的相關性減到最低。也讓所有人都可以在不互相干擾的情況下發揮比較大的效率。

[% INCLUDE header title = 'This is an HTML example' %]

Some Interesting Links


[% webpages = [ # 內嵌程式碼
{ url => 'http://foo.org', title => 'The Foo Organsiation' }
{ url => 'http://bar.org', title => 'The Bar Organsiation' }
] %]

    [% FOREACH link = webpages %] # 流程控制
  • [% link.title %] # 取得元素
    [% END %]

[% INCLUDE footer %] # 套用元件

XML::RSS

要怎麼處理RSS的檔案?XML::RSS是一個非常方便的工具。你可以透過簡單的處理,產生出一個標準的RSS。

my $rss = XML::RSS->new(); # 建立物件
$rss->parse($string); # 剖析字串
foreach my $item (@{$rss->{'items'}}) { # 處理元素
print "title: $item->{'title'}\n";
print "link: $item->{'link'}\n\n";
}
$rss->add_module( # 自訂模組
prefix => 'content', # 全文模組
uri => 'http://purl.org/my/rss/module/',
);
$rss->add_item( # 新增元素
title => $title,
link => $link,
content => { encoded => $text },
);
$rss->{output} = '1.0'; # 轉換版本
print $rss->as_string; # 印出 XML

DBI

DBI幾乎是現在寫程式必備的模組之一了,當然他本身也已經是一種標準。因此了解DBI的使用方式顯然是一個重要的課題。

my $dbh = DBI->connect( # 連結資料庫
"DBI:mysql:database=test;host=localhost",
"Melody", "Nelson", {'RaiseError' => 1}
);
eval { $dbh->do("DROP TABLE foo") }; # 卸除資料庫
# 建立資料庫
$dbh->do("CREATE TABLE foo (id INTEGER, name VARCHAR(20))");
# 插入資料列(用 quote 進行引括)
$dbh->do("INSERT INTO foo VALUES (1, " . $dbh->quote("Tim") . ")");
# 插入資料列(用 ? 進行引括)
$dbh->do("INSERT INTO foo VALUES (?, ?)", undef, 2, "Jochen");
my $sth = $dbh->prepare("SELECT * FROM foo"); # 準備選取資料列
$sth->execute; # 執行選取
while (my $ref = $sth->fetchrow_hashref()) { # 選取成雜湊
print "Found a row: id = $ref->{'id'}, name = $ref->{'name'}\n";
}
$sth->finish; # 結束查詢
$dbh->disconnect; # 結束資料庫連線

YAML

我們都知道在Perl中有陣列,雜湊,或陣列的陣列,雜湊的雜湊,或雜湊的陣列,雜湊的雜湊......,這麼複雜的資料結構,透過YAML可以讓我們清楚的一目了然。這樣才不會讓程式設計師到最後自己都搞不清楚資料的結構到底是甚麼樣了。

# 將複雜的資料結構傾印成跨平台、跨語言、簡潔易讀的文件格式
print Dump { 'P3P' => {
'Date' => [ '2003-02-07T10:00:00', '2003-02-09T12:00:00' ],
'Entry Fee' => 'USD$6',
'Hosted-By' => 'Taipei Perl Mongers',
'URL' => \('http://p3p.elixus.org/'),
} };

# 結果如下, 比 XML 漂亮多了吧. :-)
--- #YAML:1.0
P3P:
Date:
- 2003-02-07T10:00:00
- 2003-02-09T12:00:00
Entry Fee: USD$6
Hosted-By: Taipei Perl Mongers
URL: !perl/ref:
=: http://p3p.elixus.org/

Storable

在Perl中,怎麼簡單的儲存資料。如果你只想單純的把一些資料記下,又不想勞師動眾的安裝巨大的資料庫,還要煩惱資料庫的規劃。隨手使用Storable也許是個不錯的解決方式。

store \%table, 'file'; # 傾印 \%table 到二進制檔案 file
$hashref = retrieve('file'); # 讀回 \%table
nstore \%table, 'file'; # 跨平台的傾印格式
$hashref = retrieve('file'); # 相同的讀法

store_fd \@array, \*STDOUT; # 存進檔案代號
nstore_fd \%table, \*STDOUT; # 存進檔案代號(跨平台)
$aryref = fd_retrieve(\*SOCKET); # 從網路 socket 讀取
$hashref = fd_retrieve(\*SOCKET); # 從網路 socket 讀取

$serialized = freeze \%table; # 存進純量變數中
%clone = %{ thaw($serialized) }; # 解開成等價的雜湊
$cloneref = dclone($ref); # 也可以這樣寫

lock_store \%table, 'file'; # 非強制式鎖定寫入
lock_nstore \%table, 'file'; # 同上(跨平台)
$hashref = lock_retrieve('file'); # 非強制式鎖定讀取

BerkeleyDB

當然,你也可以使用簡單的資料庫來儲存,而這時候BerkeleyDB就是一個很好的選擇。我們也在內文中提過這個部份,在這裡大家可以作為一個備忘。

tie my %h, "BerkeleyDB::Hash", # 繫結 %h 雜湊
-Filename => 'test.db', # 連到 test.db 資料庫
-Flags => DB_CREATE, # 若不存在,即行建立
or die "$BerkeleyDB::Error: $!\n" ;

# 將鍵/值對加入檔案中
$h{"蘋果"} = "紅";
$h{"柳橙"} = "橙";
$h{"香蕉"} = "黃";
$h{"蕃茄"} = "紅";
# 檢查某個鍵存在與否
print "香蕉船!\n\n" if $h{"香蕉"};
# 刪除鍵/值對
delete $h{"蘋果"};
# 印出資料檔的內容
while (my ($k, $v) = each %h) { print "$k -> $v\n" }
# 解除繫結
untie(%h);

Inline::Files

當然,還有神奇的方式來儲存資料,也就是儲存在程式本身。例如我們以前常看到的網站計數器,既然我們儲存的只是一個整數,那麼把他儲存在程式中也是一個不錯的方法,而Inline::Files就可以達到這樣的效果。

# 簡單的計數器
open COUNT or die $!; # 開啟虛擬檔案 __COUNT__
my $count = ; # 讀進目前紀錄
open COUNT, ">$COUNT" or die $!; # 撰寫虛擬檔案 __COUNT__
print COUNT ++$count; # 寫入新的值
open DATE, ">$DATE" or die $!; # 撰寫虛擬檔案 __DATE__
print DATE scalar localtime; # 寫入更新日期

__COUNT__
1
__DATE__
Sat Feb 8 11:01:33 CST 2003

Devel::DProf

如果你打算為你的程式進行最佳化,那麼檢查程式的執行時間是有必要的,你可以發現程式的瓶頸是在那裡發生的。並且判斷每個部份的重要性以及對整體效率的影響。

# 執行效能分析, 寫入紀錄檔 tmon.out
% perl -d:DProf /usr/local/bin/cpanp -m Foobar
# 分析紀錄檔, 印出效能報表
% dprofpp
Total Elapsed Time = 5.074083 Seconds # 總執行時間
User+System Time = 4.053218 Seconds # 實際使用時間
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c Name
40.4 1.641 1.641 2 0.8203 0.8203 Storable::net_pstore
28.3 1.148 1.148 3 0.3828 0.3828 Storable::pretrieve
5.97 0.242 0.241 7 0.0346 0.0345 CPANPLUS::Internals::Search::BEGIN
4.59 0.186 0.271 14 0.0133 0.0194 CPANPLUS::Configure::Setup::BEGIN
2.66 0.108 0.179 23 0.0047 0.0078 CPANPLUS::Internals::BEGIN

Inline

常常有人抱怨用Perl寫出來的程式效率比較差,如果你把Perl拿來跟C或甚至組合語言相比,那麼結果大概很容易想像。可是好消息是你可以把程式中最強調效率的部份用C來寫,而且你確實可以在Perl裡面寫C,只要你用上Inline模組。不單單只是C,Inline還包含了Java,Assembley或各式各樣的其他程式語言。讓大家都能夠得心應手的把各種語言跟Perl「黏」在一起。

# 內嵌 C 語言函式
use Inline C => 'void greet(char *x) { printf("Hello, %s!\n", x); }';
greet("World"); # 印出 "Hello, World!\n"

# 內嵌 Python 函式
use Inline Python => '
def Foo():
class Bar:
def __init__(self):
print "new Bar()"
def tank():
return 10
return Bar()
';

my $o = Foo(); # 建立物件
print $o->tank; # 印出 10

# 還支援組合語言、Awk、BC、Basic、Befunge、C++、Guile、Java、Ruby、Tcl...

Locale::Maketext::Lexicon

程式的國際化(i18n)某種程度而言是非常必要的,因此藉由Locale::Maketext::Lexicon就可以幫我們處理一大堆的瑣事。接下來只要有完整的語系檔,就可以讓應用程式說出各式各樣的語言。

% xgettext.pl *.pl # 將目錄下所有 .pl 檔內的可譯詞解到 messages.po 內

# 撰寫國際化程式的好工具
use base 'Locale::Maketext'; # 採用 Maketext 本土化架構
use Locale::Maketext::Lexicon { # 定義詞典檔
en => [ 'Auto' ], # 以英文為基底語言
de => [ Gettext => 'de.po' ], # 從 de.po 讀入德文詞典
fr => [ Tie => [ DB_File => 'fr.db' ]], # 從 fr.db 讀入法文詞典
zh_tw => [ Gettext => \*DATA ], # 從 __DATA__ 讀入中文詞典
};
my $h = __PACKAGE__->get_handle; # 自動取得使用者語系
print $h->maketext("Hello, [_1]!", "Perl"); # 印出本土化的訊息
__DATA__
msgid "Hello, %1!"
msgstr "%1 您好!"

Log::Dispatch

程式執行時經常都會有意外發生,或是程式本身的錯誤,還是操作上的問題。而要排除這些問題,最好的方式就是在意外發生時能紀錄下意外發生的狀況。而且還可以在有重大的意外狀況時通知管理者儘速排除。

my $log = Log::Dispatch->new; # 建立紀錄物件
$log->add( Log::Dispatch::File->new( # 新增紀錄檔物件
name => 'file', # 物件名稱
min_level => 'debug', # 紀錄門檻
filename => '/var/log/test.log', # 紀錄檔名
) );
$log->add( Log::Dispatch::Email::MailSend->new( # 新增郵件紀錄物件
name => 'email', # 物件名稱
min_level => 'emergency', # 紀錄門檻
to => [ qw( foo@bar.com bar@baz.org ) ], # 收件地址
subject => '救命啊!!!', # 郵件標題
) );
$log->info("系統啟動中..."); # 存到紀錄檔裡
$log->error("磁碟空間不足..."); # 同上
$log->emergency("記憶體損毀!"); # 送出 Email

Test::More

測試當然是寫程式重要的過程跟檢驗之一。而如果能有方便的工具讓程式設計師不用煩惱怎麼做測試,那應該會提昇不少工作效率。這時後用Test::More應該是很好的選擇。

use Test::More tests => 16; # 測試數量
use_ok('CGI'); # 匯入模組
require_ok('Test::More'); # 使用模組
ok( "空" eq "空", '空即是空' ); # 真值檢查
is( "色", "色", '色即是色' ); # 字串相等
isnt( "空", "色", '空不是色'); # 字串不等
isn't("色", "空", '色不是空'); # 字串不等
like("空空", '/^空/', '空空如也'); # 字串比對
isa_ok(CGI->new, 'CGI', '物件類別'); # 物件類別
eq_array([1..3], [1..3], '陣列相等'); # 陣列相等
cmp_ok(1+1, '==', 2, '數值相等'); # 數值相等
is_deeply($ref1, $ref2, '複雜結構'); # 複雜結構
can_ok('Test::More', qw(ok is isnt like skip), '方法測試');

Regexp::Common

正規表示式確實是非常方便的工具,可是有些時候要寫出一個好的正規表示式的樣式確實非常讓人困擾的。幸好,很多情況下我們都會寫出類似的樣式,而這些樣式其實也有不少人曾經使用,我們就可以利用Regexp::Common來方便的使用這些非常一般化的樣式了。

while (<>) {
/$RE{num}{real}/ and print "內有數值: $&";
/$RE{profanity}/ and print "不雅文字: $&";
/$RE{quoted}/ and print "引號括住的字串: $&";
/$RE{delimited}{-delim=>'/'}/ and print "斜線括住的字串: $&";
/$RE{balanced}{-parens=>'()'}/ and print "對稱括號內字串: $&";
}

Parse::RecDescent

既然Perl對於文字處理的能力這麼的好,當然也有許多人拿他來進行相關的資料處理。以下就是一個處理一般文章的例子。

# 定義文法規則
$lexer = Parse::RecDescent->new(q(
lex: token(s)

token: 'I\b'
| 'see\b'
| 'on\b'
| 'by\b'
| /the\b|a\b/i
| /\w+/
));
# 進行詞彙分析
my $tokens = $lexer->lex('I see a cat on the windowsill by the door!');

Text::Autoformat

文字的格式也是另一種進行文字處理時會遇到的狀況。在Text::Autoformat,你可以設定好希望的格式,然後交由Perl幫忙排版。

# 整段編排、右邊界 50 欄、左右對齊(限英文;中文見 Lingua::ZH::Wrap)
print autoformat(q(
> Now is the Winter of our discontent made glorious Summer by this
> son of York. And all the clouds that lour'd upon our house in the
> deep bosom of the ocean buried.
- this is a very very very very long item.
- this is another very very very very very very long item.
), { all => 1, right => 50, justify => 'full' });

# 印出結果:
> Now is the Winter of our discontent made
> glorious Summer by this son of York. And all the
> clouds that lour'd upon our house in the deep
> bosom of the ocean buried.
- this is a very very very very long item.
- this is another very very very very very
very long item.

Text::Quoted

另一個分析文章的模組,我們常常在電子郵件內容或是討論區的討論串中使用其他人的引言,而如果想要分析出引言的內容,Text::Quoted就提供了這方面的功能。

# 分析引言結構
my $structure = extract(q(
> foo
> # Bar
> baz

quux
));

# 傳回結構如下:
[ [
{ text => 'foo', quoter => '>', raw => '> foo' },
[ { text => 'Bar', quoter => '> #', raw => '> # Bar' } ],
{ text => 'baz', quoter => '>', raw => '> baz' }
],
{ empty => 1 },
{ text => 'quux', quoter => '', raw => 'quux' } ]

XML::SAX



package MyHandler; # 自訂 SAX 處理器
use base 'XML::SAX::Base';
sub start_element {
my ($self, $data) = @_;
# ... 對 $data 元素進行處理 ...
$self->SUPER::start_element($data);
}

package main;
use XML::SAX::ParserFactory;
my $p = XML::SAX::ParserFactory->parser(Handler => MyHandler->new);
$p->parse_uri("foo.xml"); # 利用 MyHandler 剖析 foo.xml
$p->parse_string(""); # 剖析字串
$p->parse_file($fh); # 剖析檔案

GD

Perl也能畫圖,而且可以畫出非常漂亮的各種圖形,只要使用GD模組,就可以畫出各式各樣的圖表。

my $im = GD::Image->newFromPng('1.png'); # 讀取影像
my $blue = $im->colorAllocate(0,0,255); # 定義色彩
$im->arc(50,50,95,75,0,360,$blue); # 畫上橢圓
open IMG, '>:raw', '2.png'; print IMG $im->png; # 儲存影像

my $graph = GD::Graph::bars->new(400, 300); # 新增柱狀圖
my $gd = $graph->plot([1..10], [2..11]); # 填上資料
open IMG, '>:raw', '3.png'; print IMG $gd->png; # 儲存影像

Imager

另外,影像檔案的處理則是透過Imager來進行。你可以用Imager幫圖檔做旋轉,縮圖,調色等等......。

my $img = Imager->new; # 建立物件
$img->open( file => '1.png', type => 'png'); # 讀取影像
$img = $img->scale(scalefactor => 0.5); # 1/2 縮圖
$img = $img->rotate(degrees => 20); # 旋轉圖形
$img->filter(type => 'autolevels'); # 自動調色
$img = $img->convert(preset => 'grey'); # 轉成灰階
$img->write(file => '2.png'); # 儲存影像

my $pie = Imager::Graph::Pie->new; # 新增圓餅圖
my $img = $pie->draw( # 填上資料
data => [qw( 17874757 8146372 1321544 811406 )],
labels => [qw( Apache Microsoft iPlanet Zeus )],
title => 'Netcraft Web Survey',
legend => { valign => 'bottom' },
features => [qw(labelspconly legend dropshadow)],
);
$img->write(file => '3.png'); # 儲存影像

GraphViz

這是利用模組建立起一堆節點的相關關係,並且繪製成圖檔。最有趣的是它可以自動幫忙調整節點的相互位置。

my $g = GraphViz->new; # 建立有向圖

$g->add_node('唭哩岸'); # 新增節點
$g->add_node('敦煌', label => '莫高窟'); # 附帶描述
$g->add_node('沙瓦那');

$g->add_edge('唭哩岸' => '敦煌'); # 新增路徑
$g->add_edge('唭哩岸' => '沙瓦那', label => '遙遠'); # 附帶描述
$g->add_edge('敦煌' => '唭哩岸'); # 雙向路徑

$g->as_png('graph.png'); # 儲存影像

Image::Size

轉換圖檔的長寬,尤其在網路上時更有用,可以有效管控圖檔的檔案大小,而且支援多種檔案格式。

my ($w, $h) = imgsize("test.png"); # 取得圖片長寬
my $size = html_imgsize("test.png"); # 'width="XX" height="YY"'
my @attrs = attr_imgsize("test.png"); # ('-width', 60, '-height', 40)

# 支援格式: GIF JPG XBM XPM PPM PGM PBM XV PNG MNG TIF BMP PSD SWF PCD

Image::Magick

也是一個圖形處理的模組,它可以讀入圖檔,然後進行各種處理,例如縮圖,轉換檔案等等,就像名稱一般,開始變魔術。

my $image = Image::Magick->new;
my $x = $image->Read('girl.png', 'logo.png', 'rose.png'); # 讀入三個影像
$x = $image->Crop(geometry=>'100x100+1"00"+1"00'); # 截成 100x100
$image->Annotate(font=>'kai.ttf', text=>"太神奇了!"); # 加上文字
$x = $image->Write('animation.gif'); # 存成動畫

Mail::Audit

這是郵件的處理模組,可以根據不同的條件來分配郵件,就像郵差在分派郵件一般。

my $m = Mail::Audit->new(emergency=>"~/emergency_mbox"); # 建立物件
$m->pipe("listgate cle") if $mail->from =~ /cle-devel/; # 送到管線
$m->accept("perl") if $mail->from =~ /perl/; # 接進信箱
$m->reject("nospam") if $mail->rblcheck(); # 彈回垃圾
$m->ignore if $mail->subject =~ /boring/i; # 忽略信件
$m->noexit(1); $m->accept("~/Mail/%Y%m"); $m->noexit(0); # 按月彙整
$m->accept; # 其餘接收

Mail::SpamAssassin

處理垃圾郵件,廣告郵件的好幫手,利用一整套的規則可以相當準確的判讀郵件是否為垃圾郵件。常常和Mail::Audit搭配使用,減少煩人的垃圾信。

my $m = Mail::SpamAssassin::NoMailAudit->new; # 虛擬 Mail::Audit
my $spamtest = Mail::SpamAssassin->new; # 建立過濾器
if ($spamtest->check($m)->is_spam) { # 如果是垃圾信...
$status->rewrite_mail; # ...加上說明
$m->accept("trash"); # ...丟進垃圾桶
} else {
$m->accept; # 不然則照常接收
}

# 典型的 Spam 報告如下:
SPAM: Content analysis details: (7.90 hits, 5 required)
SPAM: UNDISC_RECIPS (1.5 points) Valid-looking To "undisclosed-recipients"
SPAM: NO_REAL_NAME (1.3 points) From: does not include a real name
SPAM: HEADER_8BITS (0.4 points) Headers include 3 consecutive 8-bit characters
SPAM: SPAM_PHRASE_00_01 (0.8 points) BODY: Spam phrases score is 00 to 01 (low)
SPAM: HTML_FONT_COLOR_YELLOW (0.4 points) BODY: HTML font color is yellow
SPAM: BIG_FONT (0.3 points) BODY: FONT Size +2 and up or 3 and up
SPAM: HTML_WITH_BGCOLOR (0.3 points) BODY: HTML mail with non-white background
SPAM: DATE_IN_FUTURE_96_XX (0.5 points) Date: is 96 hours or more after Received: date

Mail::Box

簡單讀取信箱的模組,讓你的Perl程式可以代理你去讀取信箱,然後進行必要的處理。

my $mgr = Mail::Box::Manager->new; # 建立物件
my $folder = $mgr->open(folder => $ENV{MAIL}); # 開啟信箱

print $folder->name; # 印出名稱
print $folder->message(0); # 第一封信
$folder->message(3)->delete; # 刪第三封
my $emails = $folder->messages; # 信件數量

foreach ($folder->messages) {...} # 逐封處理
foreach (@$folder) {...} # 同上

# 新增一封郵件
$folder->addMessage(Mail::Box::Message->new(...));

Mail::Bulkmail

大量發送信件的模組,只需要一個存有所有收件人的文字檔,Perl就可以幫忙寄送郵件。

my $bulk = Mail::Bulkmail->new(
"LIST" => "~/my.list.txt", # 地址清單
"From" => 'not_spam@example.com', # 寄件人
"Subject" => "Test message", # 標題
"Message" => "... blah blah ..." # 內文
) or die Mail::Bulkmail->error();

$bulk->bulkmail() or die $bulk->error; # 寄出大宗郵件

posted @ 2009-03-21 17:37  mop  阅读(970)  评论(0编辑  收藏  举报