前言
Perl 不僅是通用型程式語言,更是全能型的文字處理利器。許多經典的文字處理工具,如 grep(1)、sed(1)、awk(1) 等,都能透過 perl(1) 完美模擬。雖然用 Perl 模擬時的程式碼通常比原生命令列工具長,但這正展現了 Perl 作為通用型語言的廣度,而非侷限於單一特定領域。
當前定位
儘管 Perl 已退居幕後、不再是熱門話題,但它在現代資訊環境中仍扮演關鍵角色:
- Unix 不成文標準:普遍預裝於各類系統,具備極高的環境相容性。
- 雙重特性兼備:完美融合命令列工具的靈活度與通用程式語言的完整性。
- 核心優勢依舊:在文字處理、系統管理、自動化指令碼等領域仍具強大競爭力。
- 語法影響深遠:其正規表示式(Regular Expression)設計已成為業界的實質標準。
安裝
Unix
通常會預裝。但可以用 plenv(1) 裝不同版本的 Perl。若要符合現代 Perl 的需求,建議至少裝 5.36 版。
Windows
> choco install StrawberryPerl
安裝套件的限制與轉機
在 Windows 原生環境下安裝 Perl 套件經常遭遇相容性瓶頸,這也是早期 ActivePerl 大受歡迎的主因。然而,隨著 ActiveState 調整經營方針,如今取得與部署 ActivePerl 已不如以往便利。
所幸微軟推出了 WSL,讓開發者能直接在 Windows 中建構完整的 Linux 命令列環境。因此,若您計畫使用 Perl 開發應用程式,強烈建議將核心環境建立在 Linux / Unix 系統上。至於 Windows 原生版本的 Perl,則建議將其定位為高效能的文字處理工具,無需勉強在 Windows 原生環境下進行複雜的應用程式開發。
撰寫第一個 Perl 程式
Perl 吸收了一些 Unix 命令列工具的特性,所以可以用 Perl 寫一行程式。以下 Perl 程式直接在命令列完成:
> perl -e "print \"Hello World\n\";"
Hello World
在預設情形下,Perl 會讀取命令稿。直接執行一行程式並非預設行為,所以要加上 -e 參數。這和 awk 的設計是相反的。
撰寫 Perl 命令稿
使用 Perl 命令稿的虛擬指令如下:
> perl path\to\source.pl path\to\file.txt
第一個參數是 Perl 命令稿,第二個以後的參數是要處理的文字檔案。
Perl 入門
本節簡要地列出 Perl 的基本語法。這些內容無法讓讀者變成 Perl 專家,但可以看得懂一些 Perl 程式碼,開始寫 Perl 程式。
Pragma
Pragma 用來改變 Perl 程式的行為。在網路上的 Perl 程式碼很常會看到這兩句:
use strict;
use warnings;
這兩句 pragma 分別表示使用嚴格模式和回饋警告訊息。建議在寫 Perl 命令稿時一律加上這兩個 pragma。當然在寫一行程式時就不需要了。
資料型態 (Data Type)
以下是 Perl 的資料型態:
- 純量 (scalar)
- 字串 (string)
- 數字 (numeric)
- 參考 (reference)
- 陣列 (array)
- 關連式陣列 (associative array) 或雜湊 (hash)
字串和數字 (浮點數) 為 Perl 的基本型態。參考則是用來建立複雜資料結構的。在寫一行程式時,甚少會用到參考。
陣列和雜湊屬於內建資料結構。詳見後文。
變數 (Variable)
Perl 變數的特色是帶有 sigil (前綴)。根據變數的型態使用不用的 sigil:
$:用於純量。像是$var@:用於陣列。像是@arr%:用於雜湊。像是%hash
要注意 sigil 會根據型態而變。像是 @arr 表示陣列,但陣列元素是 $arr[0]。同理,%hash 表示雜湊,而雜湊值則是 $hash{"key"}。由於 sigil 很容易讓人搞混。甚少語言採用這套規則。甚至 Raku (Perl 6) 也將 sigil 的規則簡化了。
除了程式設計者建立的變數外,Perl 有許多內建變數 (built-in variables)。這些變數提供相當多的資訊。這裡有一份 Perl 內建變數的清單。
運算子 (Operator)
以下是 Perl 的算術運算子:
$a + $b:相加$a - $b:相減$a * $b:相乘$a / $b:相除$a % $b:取餘數$a ** $b:取指數+$a:取正號。無實質效果-$a:取負號
以下是 Perl 的遞增/減運算子:
++$a:前綴遞增。先遞增再取值$a++:後綴遞增。先取值再遞增--$a:前綴遞減。先遞減再取值$a--:後綴遞減。先取值再遞減
以下是 Perl 的二元運算子:
&:且 (bitwise and)|:或 (bitwise or)^:互斥或 (bitwise xor)~:取補數<<:左移>>:右移
以下是 Perl 的字串運算子:
.:相接x:重覆
依據資料型態,Perl 有兩組比較運算子。以下是 Perl 的比較運算子:
- 比較數字 (numerical)
==:相等!=:相異、不相等>:大於>=:大於或等於<:小於<=:小於或等於<=>:相比。回傳代表兩者關係的整數
- 比較字串 (stringwise)
eq:相等 (equal to)ne:相異、不相等 (not equal to)gt:大於 (greater than)ge:大於或等於 (greater than or equal to)lt:小於 (less than)le:小於或等於 (less than or equal to)cmp:相比。回傳代表兩者關係的整數
以下是 Perl 的邏輯運算子:
- 高優先度
&&:且 (logical and)||:或 (logical or)!:非 (logical not)
- 低優先度
and:且 (logical and)or:或 (logical or)xor:互斥或 (logical xor)not:非 (logical not)
控制結構 (Control Structure)
if 敘述是基本的選擇控制結構。除了使用單一的 if 敘述外,還可以加上選擇性的 elsif 敘述和 else 敘述。以下是實例:
use strict;
use warnings;
sub rand_int {
my $min = shift;
my $max = shift;
return int(rand() * ($max - $min + 1)) + $min;
}
srand();
my $n = rand_int(-1, 1);
if ($n > 0) {
print $n, " is larger than zero", "\n";
}
elsif ($n == 0) {
print $n, " is equal to zero", "\n";
}
else {
print $n, " is smaller than zero", "\n";
}
Perl 原本沒有 switch 等效敘述,是後來才加上去的。Perl 版本的 switch 等效敘述稱為 given 敘述。given 敘述是特化的選擇控制結構,目的是簡化 if 敘述。以下是實例:
use strict;
use warnings;
use experimental qw(switch);
# Day of week.
my $dow = (localtime())[6];
given ($dow) {
when ([6, 7]) {
print "Weekend", "\n";
}
when (5) {
print "Thank God. It's Friday.", "\n";
}
when (3) {
print "Hump day", "\n";
}
default {
print "Week", "\n";
}
}
while 敘述是基本的迭代控制敘述,主要用在不特定次數的迭代。以下是實例:
use strict;
use warnings;
my $n = 1;
while ($n <= 10) {
print $n, "\n";
++$n;
}
for 敘述是特化的迭代控制敘述,主要用在特定次數的迭代。以下是實例:
use strict;
use warnings;
for (my $i = 1; $i <= 10; ++$i) {
print $i, "\n";
}
last 敘述可提早結束迴迴圈,需搭配迴圈使用。以下是實例:
use strict;
use warnings;
for (my $i = 1; $i <= 10; ++$i) {
if ($i > 5) {
last;
}
print $i, "\n";
}
next 敘述可略過一次迭代,需搭配迴圈使用。以下是實例:
use strict;
use warnings;
for (my $i = 1; $i <= 10; ++$i) {
if (0 == $i % 2) {
next;
}
print $i, "\n";
}
資料結構 (Data Structure)
Perl 內建的資料結構有陣列 (array) 和雜湊 (hash) 兩種。陣列是線性的資料結構,以自然數為索引 (index)。雜湊則是儲存鍵值對的非線性資料結構。
在 Perl 之中使用常規表示式 (Regular Expression)
Perl 的常規表示式是其主要特色之一。在寫一行程式時常規表示式相當重要。本節列出 Perl 常用的常規表示式。
- 字元對應
- 一般字元:直接一比一對應
.:對應任意單一字元[...]:對應任意數個字元[^...]:不對應任意數個字元
- 重覆
?:重覆零到多次 (greedy)*:重覆零到一次 (greedy)+:重覆一到多次 (greedy)??:重覆零到一次,不貪婪 (non-greedy)*?:重覆零到多次,不貪婪 (non-greedy)+?:重覆一到多次,不貪婪 (non-greedy){n}:重覆 n 次{n,}:重覆至少 n 次{,n}:重覆至多 n 次{n,m}:重覆 n 到 m 次
|:或 (or)(...):群組 (grouping)- 文字邊界
^$
- 常見 POSIX 特定字元 (bracket expression):用在
[...]中[:alnum:]:所有字母和數字[:word:]:[:alnum:]加_(Perl 延伸)[:alpha:]:所有字母[:lower:]:所有小寫字母[:upper:]:所有大寫字母[:digit:]:所有數字[:space:]:所有空白字元[:blank:]:空白 (space) 和 TAB
- 常見 Perl 特定字元
\w:文字。相當於[[:alnum:]_]\W:非文字。\w的反向\d:數字\D:非數字\s:空白\S:非空白\b:文字邊界\B:非文字邊界。\b的反向
撰寫 Perl 一行程式 (One Liner)
現在的程式設計者較少使用 Perl 寫應用程式。目前 Perl 的定位在簡短的命令稿和一行程式。本節展示幾個常見的 Perl 一行程式的寫法。
過濾文字
這時候 Perl 類似於 grep(1) 的角色。其虛擬指令如下:
> perl -n -e "print if m{pattern};" file01 file02 file03 ...
將 pattern 的部分用常規表示式來取代即可。
-n 算是 Perl 一行程式的特殊模式之一。以本小節的一行程式來說,相當於以下的 Perl 程式:
while (<>) {
print if m{pattern};
}
取代文字
這時候 Perl 類似於 sed(1) 的角色。其虛擬指令如下:
> perl -p -e "s{pattern}{replacement};" file01 file02 file03 ...
同上,將 pattern 的部分用常規表示式來取代即可。
-p 算是 Perl 一行程式的特殊模式之一。以本小節的一行程式來說,相當於以下的 Perl 程式:
while (<>) {
s{pattern}{replacement};
} continue {
print;
}
以下 Perl 虛擬指令用來清除程式碼尾端多餘的空白:
> perl -i -ple "s{\s+$}{};" path\to\file
-i 表示將目標檔案立即 (in-place) 修改。-l 參數會將目標文字的行尾先去除,待程式跑完後再將其接回。
以下 Perl 虛擬指令將 \n (Unix 行尾) 轉成 \r\n (Windows 行尾),相當於 unix2dos(1) 指令:
> perl -i -pe "s{\n}{\r\n};" path\to\file
承上,以下 Perl 虛擬指令將 \r\n 轉成 \n,相當於 dos2unix(1) 指令:
> perl -i -pe "s{\r\n}{\n};" path\to\file
將資料按欄位分開
如同 awk,Perl 可以將資料分欄。但預設情形下不會開啟這項特性,要自行加上 -a 參數以將資料分欄位。這時候分欄的資料會存在 @F 陣列中。參考以下指令:
> perl -a -e "print pop(@F), \"\n\";" file01 file02 file03 ...
預設的分欄字元是空白。若要修改分欄字元,則改用 -F 參數來指定分欄字元。
遞迴處理多個檔案
Perl 指令本身沒有遞迴處理檔案的能力。在撰寫一行程式時,要搭配其他工具。在 Windows 上接近 find(1) 的指令為 forfiles。其使用實例如下:
> forfiles /p src /s /m *.php /C "cmd /c perl -i -ple \"s{\s+$}{};\" @path"