由于perl語言的設計者是一位語言學家,所以和任何一種人類語言相同,perl也有很多習語。
1.為優(yōu)雅、簡潔而使用 '美元下劃線'
$_是許多操作符的默認參數(shù),有時也是一些控制語句的默認參數(shù)
# $_ 作為默認參數(shù)
print $_;
print; # the same thing
print "found it" if $_ =~ /Rosebud/;
print "found it" if /Rsosebud/; # same thing
$mod_time = -M $_; # most filehandle tests
$mod_time = -M; #same thing
foreach $_ (@list) {do_something($_)};
foreach (@list) {do_something($_)}; # same thing
while (defined($_ = <STDIN>)) {
print $_;
}
while (<STDIN>) {print} # same thing
2. 了解其它的默認參數(shù)
@_ 作為默認參數(shù)
sub foo {
my $x = shift;
}
在子程序中會默認使用@_作為參數(shù)
bar (\@bletch);
sub bar {
my @a = @{shift}; # wrong
}
如果你使用use strict;將會報錯,因為perl將{}內(nèi)的shift 理解成一個變量,而不是一個函數(shù)
Error : Global symbol @shift requires explicit package name
因此,你需要在括號內(nèi)加點什么,讓perl知道shift不是一個變量名,而是一個函數(shù)
sub bar {
my @a = @{shift()} # true (最常見的做法)
}
sub bar {
my @a = @{+shift} # '+' 相當于占位符
}
@ARGV作為默認參數(shù)
在子程序之外,shift 會把@ARGV作為默認參數(shù),知道了這一點我們可以對命令行作自定義處理
比如把所有以連字符開頭的當作開關選項,其他參數(shù)當作文件名
foreach (shfit) {
if (/^-(.*)/) {
process_option($1);
}else {
process_file($_);
}
}
當然你不需要自己處理命令行參數(shù),perl中有很多現(xiàn)成的模塊處理命令行參數(shù),如Getopt::Long
其他使用'美元下劃線'的函數(shù)
- X filetests (expect for -t)
abs
chomp
chop
defined
glob
lc
print
reverse (in scalar context only)
rmdir
split
unlink
等等一些其他的內(nèi)建函數(shù)
默認使用STDIN
多數(shù)文件測試符都以$_作為默認參數(shù),但-t卻以STDIN文件句柄作為默認參數(shù)
3.常用簡寫和雙關語
使用列表賦值進行變量對調(diào)
perl并沒有特殊的操作符用于兩個變量值的互換,perl 會先計算等號右邊的表達式,然后按對應位置賦值
($a,$b) = ($b,$a); #swap $a and $b
($c,$a,$b) = ($a,$b,$c);
# 切片讓你隨意交換數(shù)組內(nèi)容
@a[1,2,3] = @a[3,2,1];
@a[map {$_*2 + 1,$_*2} 0 .. ($#a / 2)] = @a; # 數(shù)組奇偶元素對調(diào)
用[] or ()[] 強制列表上下文
有些時候你需要強制perl在列表上下文計算某個表達式。比如用正則表達式切分字符串
# 按+號切分$_z中:之前的部分
my ($str) = /([^:]*)/; #這里是列表上下文,標量上下文不會返回匹配到的子串
my @words = split /\+/, $str
對于上面兩行代碼,可以進行整形為一行代碼并省去中間變量$str
my @words = split /\+/, (/([^:]*)/)[0]; # 利用切片轉為列表上下文
使用=> 構造鍵值對
大箭頭操作符其實和逗號操作符差不多,唯一的區(qū)別是:如果=>左邊標識符能識別為一個單詞,那么perl 就會自動把它作為字符串,而不是函數(shù)調(diào)用
my %elements = (
'ag' => 47,
'au' => 79
)
在表示哈希鍵的地方可以省略引號
my %elements = (
ag => 47,
au => 79,
)
有時=>也可以省略,如果鍵和值都是沒有空格的子串,可以用qw操作符
my %elements = qw(
ag 47
au 79
)
使用=>表示方向
另一個關于=>操作符的用法,指明操作方向,比如在rename函數(shù)中,用它表示從舊文件名改為新文件名
rename "$file.c" => "$file.c.old";
小心使用{}
使用{}時請?zhí)貏e小心,它大概是perl里面功能最多的標點符號了,{}可以圈定程序塊,用作變量名分隔符,創(chuàng)建匿名哈?;蛘哌M行哈希元素的存取、解引用,等等。
如果看到{}內(nèi)有個孤零零的+號,貌似毫無必要,那它可能是有意放到那里的,可借此對語法進行修正
比如對某個返回數(shù)組引用的函數(shù)進行解引用操作時,將函數(shù)名放在{},會被當成軟引用
my @a = @{func_returning_aryref}; # wrong
下面三種方式可以給perl提示,{}內(nèi)其實是一個函數(shù)
my @a = @{func_returning_aryref()}; # ok -- 有括號
my @a = @{&func_returning_aryref}; #ok -- 有'&'
my @a = @{+func_returning_aryref}; #ok -- 有'+'
# 避免軟引用
$not_array_ref = 'buster';
@{$not_array_ref} = qw (1 2 3); # really @buster
用@{[ ]}或 eval {} 的形式來復制列表
有時候我們希望操作的是某個列表的副本,以免原始數(shù)據(jù)遭到破壞
比如查找缺失.h的頭文件,可以先讀取所有.c文件,然后將文件名換為.h結尾,再按此列表核對哪些文件不存在
my @cfiles_copy = @cfiles;
my @missing_h = grep {s/\.c$/\.h/ and not -e } @cfiles_copy;
my @missing_h = grep {s/\.c$/\.h/ and not -e} @{ [@cfiles] };
另一種高效的方法
my @missing_h = grep {s/\.c$/\.h/ and not -e} eval {@cfiles};
4. 避免過多的標點
無括號方式調(diào)用子程序
調(diào)用子程序時,前面的&和后面的括號都是可以省略的
&myfunc (1,2,3);
myfunc (1,2,3);
myfunc 1,2,3; #之前需要有函數(shù)定義或聲明
myfunc 1,2,3; #將會出錯
sub myfunc {}
因此可以提前聲明主程序
use subs qw(myfunc);
myfunc 1,2,3;
sub myfunc {}
用and 和or 代替&&和| |
and 和or 在操作符結合優(yōu)先級中位于最低一級,使用這兩可以免去對表達式的分組,有效減少圓括號的使用
print ("hello") && print "goodbye";
print "hello" and print "goodbye";
(my $size = -s $file) | | die "$file has zero size\n";
my $size = -s $file or die "$file has zero size\n";
此外,花括號中的最后一個分號可以省略
my @caps = map {uc $_;} @words;
my @caps = map {uc $_} @words;
還有一種是使用表達式修飾可以省略圓括號和花括號,"后向條件式用法"
if (/^_END_$/) {last}
last if /^_END_$/; # 非常簡便
5. 調(diào)整列表格式便于維護
雖然前面說到要避免標點符號的無謂使用,但凡事無絕對,有些情況添加標點符號很有用處
perl 允許你在列表末尾加一逗號
my @cats = ('a','b','c',); 這樣添加新元素時就不會擔心忘記添加逗號而影響數(shù)據(jù)結構
push @cats ,'d';
6. 合理使用foreach , map , grep
Perl 提供了好幾種遍歷列表元素的方法
在perl中避免使用for循環(huán)和按下標遍歷列表的方式,相比其他循環(huán)方式慢
for (my $i = 0 ; $i <= @array ; $i++) {
print "I saw $array[$i]\n";
}
大多數(shù)喜歡使用foreach ,map和grep,這三種方法有相似之處,不過各自應用的場合不同,雖然這三種方式可以相互轉換。
通過foreach來進行列表元素的只讀遍歷
如果是僅僅遍歷列表中的所有元素,那么foreach 就可以
foreach my $cost (@cost) {
$total += $cost;
}
foreach my $file (glob '*') {
print "$file\n" if -T $file;
}
使用foreach的地方都可以使用for來代替
for (@lines) {
print ,last if /^From:/;
}
用map函數(shù)從現(xiàn)有列表延展出新列表
如果從現(xiàn)有列表推導出新列表,請使用map(列表上下文)
my @sizes = map {-s } @files;
my @sizes = map -s,@files;
我們一般會直接對默認的$_變量操作,它其實相當于當前列表元素的別名
所以一旦在map表達式中修改了$_的內(nèi)容的話,原始數(shù)據(jù)也會隨之改變
請記住一個原則確實要修改原始列表內(nèi)容的話,請使用foreach
my @digitless = map {tr/0-9//d; $_} @elems;
實際上在修改原始列表,為了避免修改原始變量,可以復制$_到詞域變量再做改動處理
my @digitless = map {
(my $x = $_) =~ tr/0-9//d;
$x
} @elems;
用foreach修改元素內(nèi)容
如果目的是修改元素的內(nèi)容,請使用foreach循環(huán)。和map、grep一樣,循環(huán)體中的控制變量其實是列表當前元素的別名,所以修改控制變量,實際上修改原始數(shù)據(jù)
foreach my $num (@number) {
$num *= 2;
}
grep篩選列表元素
grep一般用于篩選列表元素或對元素計數(shù)
print grep /abc/i,@lines;
在標量上下文返回符合條件的元素個數(shù)
my $selnum = grep /*.txt/, @files;
7. 掌握多種排序方式
在perl最基本的排序操作中,所用的代碼相當簡潔,僅僅使用一個sort操作符就可以對列表進行排序,它會返回排序后的新列表。
my @sortnum = sort qw(hell liu chen) # 默認按照UTF-8排序規(guī)則進行排序
print join ' ' ,sort 1 .. 10;
如果不想用默認的UTF-8排序,那你就需要自己編寫用于比較的子程序
Sort子程序
所謂perl排序子程序,實際上并非完整的排序算法,可以說是比較子程序,和一般的子程序不同,排序子程序得到的參數(shù)是經(jīng)過硬編碼的兩個特殊包變量b,而非@_。
b在排序子程序內(nèi)部是本地化的,好比一個隱形的local(
b)語句一樣 (但不需要我們自己寫)
perl內(nèi)建的排序方式相當于用cmp操作符比較
print sort 1 .. 10;
sub utf8ly {$a cmp $b}
print sort utf8ly 1 .. 10; #等價于內(nèi)置sort函數(shù)
print sort {$a cmp $b} 1 .. 10 # sort后面直接使用代碼塊,簡便
# $a 和 $b 的排序方法決定了最終得到的排序順序
print sort {$a <=> $b} 1 .. 10 #按數(shù)字大小進行排序
print sort {lc($a) cmp lc($b)} qw(this is a test) # 進行大小寫無關的排序
print sort {$b <=> $a} 1 .. 10 # 調(diào)換$a和$b的位置實現(xiàn)倒序排序
print sort {-M $a <=> -M $b} @files; #可以根據(jù)$a和$b的值計算后再作比較
my %hash = (b => 1,c => 2,d =>5);
print sort {$hash{$a} <=> $hash{$b}} keys %hash; #根據(jù)哈希值的大小對哈希鍵進行排序
高級排序:一般做法
有時在比較兩個值的時候需要進行大量計算,比如按照密碼文件的第三個字段進行排序
open my ($passwd),'/etc/passwd' or die "$!";
my @byuid = sort {(split /:/,$a)[2] <=> (split /:/,$b)[2] } <$passwd>
上面雖然可以實現(xiàn)目標,但是邊排序邊計算很慢,因此在比較前先計算好要比較的數(shù)據(jù),提高排序速度
# 以原始數(shù)據(jù)行為key,uid為key,然后對哈希進行排序即可
open my ($passwd),'/etc/passwd' or die "$!";
my @lines = <$passwd>;
my %hash = map {$_,(split /:/)[2]} @passwd;
my @sortlines = sort {$hash{$a} <=> $hash{$b}} keys %hash;
高級排序:更酷的做法
上面方法的缺點在于需要額外準備一條語句,建立輔助排序的數(shù)組和哈希,我們可以使用 || = 操作符來簡化
{
my %m;
my @files = glob '*';
my @sorted = sort {($m{$a} ||= -M $a) <=> ($m{$b} ||= -M $b)} @files;
}
"$m{$a} ||= -M $a"實現(xiàn)方式: 第一次遇到某個文件$a時,$m{$a}的值是undef,于是||=后的表達式-M $a求值后存入$m{$a}
施瓦茨變換
目前為止最精簡的全能排序技術還得算是施瓦茨變換,即若干map實現(xiàn)的sort排序
my @name = glob '*';
my @sortnames = map { $_ -> [0] } #4.提取原始文件名
sort { $a -> [1]
<=>
$b -> [1] } #3.對[name,key]數(shù)組排序
map { [$_,-M] } #2.創(chuàng)建[name,key]數(shù)組
@name; #1.原始數(shù)據(jù)
從下往上閱讀,你會發(fā)現(xiàn)上面的代碼巧妙,省略了不需要的中間變量
單個元素的排序也可以借鑒這個思路,只要在匿名數(shù)組中修改右邊的元素值
open IN,"/etc/passwd" or die "$!";
my @sort_uid =map { $_ -> [0] }
sort {$a -> [1] <=> $b -> [1] }
map { [$_,(split /:/)[2] ] }
<IN>;
8.通過智能匹配簡化工作
perl 5.10 引入了智能匹配操作符~~,該操作符能用最少的指令寫出功能強大的條件判斷式 ,通常和given-when一起聯(lián)合使用。注意確保perl版本在5.10.1以上
use 5.010001;
檢查哈希鍵或數(shù)組元素是否存在
if (exists $hash{$key}) { ... }
if (grep {$_ eq $name} @cats) { ... }
#智能匹配操作符
if ($key ~~ $hash) { ... }
if ($name ~~ @cats) { ... }
通過正則表達式檢查元素是否存在
my $matched = 0;
foreach my $key (keys %hash) {
do {$matched = 1; last } if $key =~ /$regex/;
}
if ($matched) {
print "one of the keys matched\n";
}
#智能匹配操作符
if (%hash ~~ /$regex/) {
say "one of the keys matched";
}
if (@array ~~ /$regex/) {
say "one of the elements matched";
}
其他通過智能匹配可以大大簡化代碼的操作有:
%hash1 ~~ %hash2 #兩個哈希是否有相同的鍵值
@array1 ~~ @array2 #數(shù)組是否相同
%hash ~~ @keys #數(shù)組中是否存在某個鍵
9.用given-when 構造switch語句
swith語句源于c語言中,在perl 5.10 得以實現(xiàn)
更少的輸入
perl給switch起了一個新的名字,叫做given-when。
你也許已經(jīng)會用if-elsif-else語句表達
my $dog = 'spot';
if ($dog eq 'fido') { ... }
elsif ($dog eq 'Rover') { ... }
elsif ($dor eq 'spot') { ... }
else { ... }
根據(jù)$dog 種類需要運行不同的代碼,這種老式寫法要求每個條件分支都重復一遍相近的判斷語句,太過繁瑣
given ($dog) {
when {'fido'} { ... }
when {'Rover'} { ... }
when {'spot'} { ... }
default { ... };
};
實際上given-when工作方式是先將$dog賦值給$_,然后perl自動完成$_和給定數(shù)據(jù)間的比較
智能匹配
在上例中,perl是如何知道該用字符比較操作符?事實上,除非你明確指定比較操作符,默認情況下,when語句總是自動使用智能匹配操作符~~
when ('fido') { ... }
when ($_ ~~ 'fido') { ... } #與上式等價
多分支處理
默認情況下,只要某個when塊得到匹配,這段程序的代碼就算運行結束了,perl不再會計算其他when塊,這點和if-elsif-else類似,好比每個when塊末尾都有一個隱形的break
given ($dog) {
when {'fido'} { ... ; break}
when {'Rover'} { ... ; break}
when {'spot'} { ... ; break}
default { ... };
};
利用continue可以使程序在當前when塊運行結束后進行下一個when繼續(xù)比較
given ($dog) {
when {/o/} { ...; continue }
when {/t/} { ...; continue }
when {/d/} { ...; continue }
};
上述會進行所有when測試
代碼組合
if-elsif-else結構還有一個缺陷是,它不能在兩個條件中組合其他代碼。任何代碼都必須找到匹配條件后才能執(zhí)行。而在given-when結構中,你可以在when塊之間自由插入任意代碼,哪怕是中途修改主題變量也沒問題。
use 5.010;
given ($dog) {
say "I'm working with [$_]";
when {/o/} { ...; continue }
say "continuing to look for a t";
when {/t/} { ...; continue }
$_ =~ tr/p/d/;
when {/d/} { ...; continue }
};
對列表進行分支判斷
在foreach循環(huán)中我們也能用when,這和在given中相似,只不過它是依次從列表取測試目標
my $count = 0;
foreach (@array) {
when (/[aeiou]$/) {$vowels_count++}
when (/^[aeiou]$/) {$count++}
}
say "\@array contains $count words ending in consonants and $vowel_count words ending in vowels";
10.用do{}創(chuàng)建內(nèi)聯(lián)子程序
do {} 這種語法能把幾條語句組合成單條表達式,這有點類似與內(nèi)聯(lián)子程序
my $file = do {
local $/;
open IN, "$filename" or die "$!";
<IN>; #最后一條表達式 會作為代碼塊的返回值返回
};
若是不用do寫
my $file;
{
local $/;
open IN, "$filename" or die "$!";
$file = join ' ' ,<IN>; #代碼塊中的數(shù)據(jù)周期有限,因此需要賦值才能將數(shù)據(jù)傳遞到外部
};
借助if-elsif-else結構返回不同數(shù)據(jù)的寫法,也可以歸納為do {}的形式,能省略大量代碼,且語義更加清晰
my ($thousands_sep,$decimal_sep);
if ($locale eq 'European') {
($thousands_sep,$decimal_sep) = qw (. ,);
}elsif ($locale eq 'English') {
($thousands_sep,$decimal_sep) = qw (, .);
}
my ($thousands_sep,$decimal_sep) = do {
if ($locale eq 'European') {qw (. ,)}
elsif ($locale eq 'English') {qw (, .)}
};
11.用List::Util和List::MoreUtils簡化列表處理
掌握列表的各項操作,是深入理解perl的必經(jīng)之路,perl內(nèi)置的map、foreach、grep語句,組合起來能完成許多復雜的列表處理。而有些列表操作極其常見,與其發(fā)明輪子,還不如直接用List::Util和List::MoreUtils這兩個c語言實現(xiàn)模塊提供的函數(shù)和來得更快
快速查找最大最
查找列表中的最大值,你自己寫的話也不算太麻煩
my @array;
my $max = $array[0];
foreach (@array) {
$max = $_ if $_ >$max;
}
# 不過純粹用perl實現(xiàn),相對來說性能要差些
# List::Util模塊中提供了c語言實現(xiàn)的max程序,可以直接返回列表中的最大值
use List::Util qw(max);
my $max = max(@array); # 類似還有min函數(shù)
use List::Util qw(maxstr); # 類似還有minstr函數(shù)
my $max_string = maxstr(@array) #返回列表中最大的字符串
# 求和
my $sum = 0;
foreach (@array) {
$sum += $_;
}
# 而List::Util提供的sum程序則更加方便
use List::Util qw(sum);
my $sum = sum(@aarray);
列表合并
對一系列數(shù)字求和的方法還有一個,就是利用List::Util模塊提供的reduce函數(shù)逐項迭代,執(zhí)行速度很快
use List::Util qw(reduce);
my $sum = reduce {$a + $b} @array;
#與sort類似,reduce也以代碼塊作為參數(shù),不過運行機制稍有不同,每次迭代,會從列表中抽取前兩個元素,分別設置別名$a和$b,這樣參數(shù)列表的長度就會縮短兩個元素,然后reduce把代碼塊計算的結果再壓回參數(shù)列表的頭部,如此往復,直到最后列表里只剩一個元素
my $produce = reduce {$a * $b} @array; #累乘
# 為了方便該模塊創(chuàng)建了累乘的函數(shù)product
use List::Util qw(product);
my $produce = product @array;
提取列表頭尾元素
use List::Util qw(head);
Usage: head $size ,@list;
my @results = head 2, qw(foo bar baz); #返回 foo bar
my @results = head -1 qw(foo bar baz); # foo bar 負數(shù)表示返回所有除了最后的$size個元素
use List::Util qw(tail);
Usage: tail $size, @list;
my @results = tail 2, qw(foo bar baz); #返回 bar baz
my @results = tail -1 qw(foo bar baz); # baz 負數(shù)表示返回所有除了開始的$size個元素
判斷是否有元素匹配
純粹用perl實現(xiàn)的話,找出列表中第一個符合某項條件的元素,比找出所有符合條件的要麻煩一些
my $found = grep {$_ > 1000 } @array;
若@array中有許多元素,而第一個元素就是1001,上面的代碼照舊檢查每一個元素,當然我們可以自行控制退出循環(huán)
my $found = 0;
foreach (@array) {
$found = $_ if $_ > 1000;
last if $found;
}
# 每次寫這一串很麻煩,List::Util中的first程序就是為了解決這個問題
use List::Util qw(first);
my $found = first {$_ > 1000} @array
# 此外,List::MoreUtils模塊中,也提供了許多額外的函數(shù)
use List::MoreUtils qw(any all none notall)
my $found = any {$_ >1000} @list;
my $found = all {$_ >1000} @list;
my $found = none {$_ >1000} @list;
my $found = notall {$_ % 2} @list;
一次遍歷多個列表
有時候我們手上有幾個相互關聯(lián)的列表需要同時遍歷,最普通的做法是利用數(shù)組下標,同步提取對應與元素,計算后存入另一個列表
my @a = ( ... );
my @b = ( ... );
my @c;
foreach (0 .. $#a) {
my ($a,$b) = ($a[$_],$b[$_])
push @c, $a + $b;
}
#用List::MoreUtils中的pairwise子程序,以漂亮的方式呈現(xiàn)
use List::MoreUtils qw(pairwise);
my @c = pairwise {$a + $b} @a, @b; #pairwise只適用于兩個列表
# 對于三個及以上列表的同步計算,可以使用each_array子程序
use List::MoreUtils qw(each_array);
my $ea = each_array (@a,@b,@c);
my @d;
while (my ($a,$b,$c) = $ea ->() ) {
push @d,$a + $b + $c;
}
數(shù)組合并
合并多個數(shù)組雖然可以自己寫,但還不如List::MoreUtils中的mesh子程序方便
use List::MoreUtils qw(mesh);
my @odds = qw (1 3 5 7 9);
my @evens = qw (2 4 6 8 10);
my @numbers = mesh @odds, @evens;
對列表進行去重
use List::Util qw(uniq);
my @num = qw (ab AB AB 1 1.0 2);
my @uniq = uniq @num; # ab AB 1 1.0 2
#uniqnum對數(shù)字去重
use List::Util qw(uniqnum);
my @num = qw (1 1.0 2 3 3 4 1);
my @uniq = uniqnum @num; # 1 2 3 4
12.用autodie簡化錯誤處理
perl有許多內(nèi)置函數(shù)都是系統(tǒng)調(diào)用,可能會因為不可控制的原因而失敗,所以有必要對最終運行結果作檢查。加上錯誤處理的代碼會讓整個程序看上去比較亂,有一種方法可以避免手工輸入這類代碼,即用autodie編譯指令
use autodie;
opne IN,"$file"; #會自動加入die相關的檢查
# 默認情況下,autodie會對它能起作用的所有函數(shù)生效,包括絕大多數(shù)內(nèi)建的同系統(tǒng)底層交互的函數(shù)
# 也可指定對某些特定函數(shù)起作用
use autodie qw(open close);