位元詩人 Perl:Unix 不成文標準

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

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}:重覆 nm
  • |:或 (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"

另見

關於作者

位元詩人 (ByteBard) 是資訊領域碩士,喜歡用開源技術來解決各式各樣的問題。這類技術跨平台、重用性高、技術生命長。

除了開源技術以外,位元詩人喜歡日本料理和黑咖啡,會一些日文,有時會自助旅行。