位元詩人 [Windows] 程式設計教學:在 Windows 上使用 Perl

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

Perl 不僅是通用型程式語言,還是全能型文字處理工具。一些常見的文字處理工具,像是 grep(1)sed(1)awk(1) 等,都可以用 perl(1) 來模擬,只是用 Perl 來模擬時程式碼會比原先的命令列工具長一點。這是因為 Perl 是通用型工具,不像其他文字處理工具的目標領域較窄。

相對於如日中天的 Python,Perl 正在式微。所以,本文不會深入地介紹 Perl,而會假定程式設計者在 Windows 上使用 Perl 寫一行程式 (one liner) 和簡短的命令稿 (script) 為目標來學習 Perl。

安裝 Perl

Windows 上的 Perl 發行版有 ActivePerl 和 StrawberryPerl 兩種版本。前者是商業軟體,但有免費版本。後者則是自由軟體。兩者功能雷同,擇一安裝即可。

以下指令安裝 ActivePerl:

> choco install ActivePerl

以下指令安裝 StrawberryPerl:

> choco install StrawberryPerl

撰寫第一個 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) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。