本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

Perl 速查 — 簡明參考

Perl 5.38 語法、正則、引用與最常用內置函數速查手冊,覆蓋約 80% 日常場景。

Pl

Perl Perl 5.38

perl 5 (interpreter) · 多範式(過程式 · OO · 函數式) · 動態

學習路徑

先學會 perl hello.pl 運行與 use strict/warnings → 掌握標量、數組、哈希與流程控制 → 深入子程序與上下文 → 用引用構建複雜數據結構 → 用正則處理文本 → 理解 eval 錯誤處理與 bless 面向對象 → 再按需學線程、網絡、時間與 CPAN 模塊。FAQ 節適合回頭避坑。

1.Hello World 與構建環境

運行 Perl 腳本、use strict/warnings 與命令行工具。

最小程序

保存為 hello.pl,用 perl hello.pl 運行。use strict 與 use warnings 是標配。

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
say "Hello, world!";
# 運行:
# perl hello.pl
# 第一行 shebang 指向解釋器
# say 自動換行,print 不換行

運行與解釋

perl 直接解釋執行腳本;-c 只做語法檢查,-e 執行單行代碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 直接運行腳本:
# perl hello.pl
# 語法檢查(不執行):
# perl -c hello.pl
# 命令行單行代碼:
# perl -e 'print "hi\n"'
# 打開警告:
# perl -w hello.pl
# 打印加載路徑:
# perl -V
# 逐行處理(-n/-p):
# perl -ne 'print if /error/' log.txt
# 解釋器定位:
# which perl

shebang 與參數

#!/usr/bin/perl 聲明解釋器;命令行參數存於 @ARGV,$0 是腳本名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# 命令行參數存於 @ARGV:
my ($name) = @ARGV;
say "你好, $name" if defined $name;
# 運行:
# perl greet.pl Rex
# 遍歷全部參數:
for my $arg (@ARGV) {
say "參數: $arg";
}
# $0 是腳本名:
say "腳本: $0";
# 參數個數:
my $count = scalar @ARGV;

輸出與格式化

print 不換行、say 自動換行;printf 格式化、sprintf 返回字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
use feature 'say';
print "hello\n"; # 手動換行
say "world"; # 自動換行
# 格式化輸出:
printf "姓名: %s, 年齡: %d\n", "Rex", 5;
# 返回字符串:
my $msg = sprintf("%d + %d = %d", 1, 2, 3);
say $msg;
# 輸出到句柄:
print STDERR "出錯啦\n";
# 常見格式:
# %s 字符串 %d 整數 %f 浮點 %x 十六進制

讀取輸入

<STDIN> 讀取一行(含換行);chomp 去掉末尾換行;EOF 返回 undef。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 讀一行(含換行):
my $line = <STDIN>;
chomp $line; # 去掉末尾換行
say "輸入: $line";
# 讀一行整數:
my $num = <STDIN>;
chomp $num;
$num += 0; # 轉成數值
# 多行讀取直到 EOF:
while (my $l = <STDIN>) {
chomp $l;
last if $l eq 'quit';
say "讀到: $l";
}
# 無輸入時 <STDIN> 返回 undef

引號運算子

q() 單引號語義、qq() 雙引號語義、qw() 單詞列表、qx() 命令替換。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# q() 不插值:
my $a = q(原樣 $name);
# qq() 可插值:
my $b = qq(你好, $name);
# qw() 按空白拆單詞:
my @words = qw(alpha beta gamma);
# qx() 執行命令取輸出:
my $pwd = qx(pwd);
chomp $pwd;
say "目錄: $pwd";
# 好處:q 類運算子可用任意定界符:
# q{...} qq(...) qw!...!
# 避免轉義引號

use strict/warnings

use strict 強制聲明變量並禁裸字;use warnings 打開運行期警告。現代 Perl 標配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict; # 拼寫錯誤/裸字報錯
use warnings; # 運行期警告
# strict 的三面:
# strict 'vars' 變量必須聲明
# strict 'refs' 禁止符號引用
# strict 'subs' 禁止裸字函數名
# 未聲明變量編譯報錯:
# $foo = 1;
# 報 Global symbol requires explicit package name
# 聲明後正常:
my $bar = 1;
say $bar;
# 生產代碼永遠帶上這兩行

perldoc 文檔

perldoc 隨 Perl 安裝,查函數、模塊、內置變量與語言教程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 查看內置函數:
# perldoc -f map
# 查看模塊文檔:
# perldoc JSON
# 新手入門教程:
# perldoc perlintro
# 內置特殊變量:
# perldoc perlvar
# 運算符優先級:
# perldoc perlop
# 內置函數全表:
# perldoc perlfunc
# 模塊安裝位置:
# perldoc -l JSON
# 語法快速參考:
# perldoc perlrequick

2.變量與常量

標量/數組/哈希聲明、my/our/local/state 與常量定義。

變量聲明

三種符號標識三種容器:$ 標量、@ 數組、% 哈希。my 聲明詞法變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# 三種符號:
my $scalar = 42;
my @array = (1, 2, 3);
my %hash = (name => 'Rex', age => 5);
# 聲明不賦值:
my $later;
my (@a, %h);
# 批量賦值:
my ($x, $y, $z) = (1, 2, 3);
# 列表解構:
my @nums = (1, 2, 3, 4);
my ($first, @rest) = @nums;
# $first=1, @rest=(2,3,4)

標量類型

標量存一個值:數字、字符串或引用。字符串與數字自動互轉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use strict;
use warnings;
my $num = 3.14; # 數字
my $str = "hello"; # 字符串
my $bool = 1; # 假值:0 '' undef '0'
my $undef; # 未賦值是 undef
# 自動互轉(按上下文):
my $sum = "3" + 4; # 7(數值上下文)
my $s = 3 . 4; # "34"(字符串拼接)
# 字符串前綴轉數字:
my $n = "12abc" + 0; # 12
# 長度:
my $len = length $str; # 5

數組操作

數組是元素列表。push/pop 操作末尾,shift/unshift 操作開頭,下標從 0 起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
$nums[0] = 10; # 修改元素
say $nums[0];
# 棧操作:
push @nums, 4; # 末尾追加
my $last = pop @nums; # 彈出末尾
unshift @nums, 0; # 開頭插入
my $first = shift @nums; # 彈出開頭
# 長度:
my $count = scalar @nums;
# 最後元素:
my $end = $nums[-1];
# 切片:
my @sub = @nums[0, 1];

哈希操作

哈希是鍵值對,鍵為字符串。keys/values/each 遍歷,exists/delete 管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my %ages = (alice => 30, bob => 25);
# 讀取/修改:
say $ages{alice};
$ages{carol} = 28;
# 刪除與判斷:
delete $ages{bob};
exists $ages{alice}; # 鍵是否存在
# 遍歷:
for my $name (keys %ages) {
say "$name: $ages{$name}";
}
# 鍵值對同時取:
while (my ($k, $v) = each %ages) {
say "$k=$v";
}
# 注意 each 消耗游標,遍歷中勿增刪

常量定義

use constant 定義編譯期常量,無符號、像子程序一樣調用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
# use constant 定義常量:
use constant PI => 3.14159;
use constant MAX_RETRIES => 3;
# 常量像子程序一樣調用(無 $ 符號):
my $area = PI * 2 * 2;
# 常量哈希/數組:
use constant SETTINGS => { debug => 1 };
# 只讀語義:
# Readonly、Const::Fast 模塊提供
# 常量不能直接插值:
# my $s = "PI = PI"; # 不會替換
# 插值需拼接或臨時變量

作用域

my 詞法變量塊內可見;our 包變量全局;local 臨時覆蓋全局;state 靜態詞法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'state';
# my 塊作用域:
{
my $temp = 10;
say $temp; # 塊內可見
}
# 塊外 $temp 不存在
# our 包變量(全局):
our $VERSION = '1.0';
# local 臨時覆蓋特殊變量:
{
local $/; # 分隔符臨時置 undef
my $all = <$fh>; # 整個文件一次讀完
}
# 離開塊後 $/ 恢復
# state 跨調用保留:
sub counter {
state $n = 0;
return ++$n;
}
say counter(); # 1
say counter(); # 2

默認變量

$_ 默認輸入變量,@_ 參數列表;$! 系統錯誤、$0 腳本名等特殊變量常用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# $_ 默認變量:
for (1..3) {
say $_; # 循環中默認給 $_
}
# 很多函數默認操作 $_:
for (@ARGV) {
chomp;
print;
}
# @_ 參數列表:
sub sum {
my $t = 0;
$t += $_ for @_;
return $t;
}
# 常用特殊變量:
# $0 腳本名 $! 最近系統錯誤
# $/ 分隔符 $| 關閉緩衝
# $$ 進程號 $ARGV 當前文件名

字符串插值

雙引號裏 $scalar 與 @array 會插值;單引號原樣輸出;花括號限定變量邊界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @list = (1, 2);
# 雙引號插值:
my $a = "你好, $name"; # 標量
my $b = "元素: @list"; # 數組(空格分隔)
# 轉義字面 $:
my $c = "轉義: \$name";
# 單引號不插值:
my $d = '你好, $name';
# 花括號限定變量名:
my $e = "${name}s house";
# 索引/解引用插值:
my $h = [1, 2, 3];
my $f = "第二個: $h->[1]";
# 轉義:\n 換行 \t 製表 \\ 反斜槓

3.數據類型

標量、undef、真假值、列表與哈希、文件句柄與 typeglob。

標量類型

標量是唯一基本類型,可存整數、浮點、字符串或引用。無需顯式聲明類型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
my $n = 42; # 整數
my $f = 3.14; # 浮點
my $s = "text"; # 字符串
my $r = \@ARGV; # 引用
my $u; # undef
# 字符與碼點互轉:
my $ch = chr(65); # "A"
my $ord = ord('A'); # 65
# 數值進制:
my $hex = 0xFF; # 255
my $oct = 0b1010; # 10
# 大數精度:
# use bigint / Math::BigInt
# 類型隨賦值自動改變

數字字符串互轉

按上下文自動轉換:+ 數值、. 拼接。顯式轉用 +0 與 .""。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use feature 'say';
# 自動轉換:
my $sum = "3" + "4"; # 7(數值上下文)
my $cat = 3 . 4; # "34"(字符串)
# 顯式轉換:
my $n = "42" + 0; # 字符串轉數字
my $s = 42 . ""; # 數字轉字符串
# 前綴數字規則:
my $v = "12px" + 0; # 12
# 非數字開頭得 0:
my $z = "abc" + 0; # 0
# 格式化輸出:
my $out = sprintf "%.2f", 3.14159; # 3.14
# 數值與字符串比較運算符不同:
# == != < > 數值;eq ne lt gt 字符串

undef 與 defined

undef 表示未定義,在數值上下文是 0、字符串上下文是空串。defined 判斷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
my $x; # 未賦值即 undef
say "未定義" unless defined $x;
# undef 上下文值:
my $y = $x + 1; # 1(數值=0)
my $s = "值: $x"; # 值: (字符串=空)
# 判斷:
if (defined $x) {
say "已定義";
}
# 定義或賦值:
$x //= 5; # 未定義則賦 5
say $x; # 5
# 顯式置 undef:
undef $x;
# // 是定義或運算符(5.10+)

布爾真假

假值只有 undef、0、"0"、空串;其餘全真。邏輯運算符返回操作數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use feature 'say';
# 假值:undef、0、"0"、""
my @false = (undef, 0, "0", "");
# 其餘都是真:
my @true = (1, "0.0", " ", [], {});
if ("0.0") { say "是真" } # 非空字符串
if ([]) { say "數組引用真" }
# 邏輯運算符返回最後求值的操作數:
my $a = 0 || "fallback"; # "fallback"
my $b = 1 && "x"; # "x"
# 定義或:
my $c = $undef // "默認值"; # "默認值"
# 用 ? : 生成布爾:
my $ok = 5 > 3 ? 1 : 0;

數組與列表

列表字面量 (1,2,3)、qw 單詞列表、範圍 1..5。上下文決定整體或元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
my @list = (1, 2, 3);
my @empty = ();
# qw 單詞列表:
my @names = qw(alice bob carol);
# 範圍:
my @digits = (1..5); # 1 2 3 4 5
my @chars = ('a'..'e'); # a b c d e
# 列表賦值:
my ($a, $b, $c) = (1, 2, 3);
# 嵌套展開:
my @flat = (1, (2, 3), 4); # 1 2 3 4
# 上下文差異:
my @x = (1, 2); # 列表,保留全部
my $y = (1, 2); # 標量,取尾項 2
# 標量上下文取數組長度:
my $len = @x; # 2

哈希鍵值對

胖箭頭 => 自動給左邊加引號;鍵值成對出現,鍵是字符串或整數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
# 胖箭頭自動引號:
my %h = (name => 'Rex', age => 5);
# 等價寫法:
my %h2 = ('name', 'Rex', 'age', 5);
# 數字鍵:
my %num = (1 => 'one', 2 => 'two');
# 空哈希:
my %empty = ();
# 哈希轉列表:
my @kv = %h; # ('name','Rex','age',5)
# 混合寫法:
my %mixed = (x => 1, 'y', 2);
# 鍵訪問用花括號:
my $name = $h{name};
# 花括號裏可用表達式:
my $key = 'age';
my $age = $h{$key};

文件句柄

STDIN/STDOUT/STDERR 是內置句柄;open 詞法句柄讀寫文件,三參數形式最安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 內置句柄:
print STDOUT "輸出\n";
print STDERR "錯誤\n";
my $line = <STDIN>; # 讀輸入
# 三參數 open:
open my $in, '<', 'data.txt' or die $!;
my $first = <$in>;
close $in;
# 寫句柄:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 判斷句柄是否終端:
my $tty = -t STDOUT;
# 句柄是標量,可傳參:
sub dump_all {
my $fh = shift;
print $fh @_;
}

typeglob

* 前綴表示符號表項,可給標量/數組/子程序起別名。現代代碼少用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
my $scalar = 1;
my @array = (1, 2);
# typeglob 別名:
*alias = \$scalar; # 標量別名
*array_alias = \@array;
# 別名指向同一數據:
$alias = 99;
say $scalar; # 99
# 符號表包含所有同名槽:
# *name 含 $name @name %name &name
# 常見用途:
# 1. 舊式文件句柄:*FH = *STDOUT;
# 2. 函數別名:*f = \&original;
# 現代推薦用引用替代:
my $r = \$scalar;
${$r} = 100;

4.引用與解引用

Perl 用引用替代指針:反斜槓取址、解引用、箭頭操作與嵌套結構。

引用基礎

反斜槓 \ 對變量取引用,得到引用類型的標量;ref 函數返回類型名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @arr = (1, 2, 3);
my %h = (a => 1);
my $sref = \$name; # 標量引用
my $aref = \@arr; # 數組引用
my $href = \%h; # 哈希引用
# ref 返回類型名:
say ref $sref; # SCALAR
say ref $aref; # ARRAY
say ref $href; # HASH
# 非引用返回空串:
my $plain = 5;
say ref $plain; # ""
# 引用是標量,可存入數組/哈希:
my @refs = ($aref, $href);

解引用

@$ref、%$ref、$$ref 把引用還原為對應類型;花括號形式 ${$ref} 更醒目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my @arr = (10, 20, 30);
my %h = (a => 1, b => 2);
my $aref = \@arr;
my $href = \%h;
# 數組解引用:
say $aref->[0]; # 10(推薦)
say ${$aref}[0]; # 10
# 哈希解引用:
say $href->{a}; # 1
say ${$href}{a}; # 1
# 標量解引用:
my $val = 99;
my $sref = \$val;
say $$sref; # 99
say ${$sref}; # 99
# 整體取出:
my @copy = @$aref; # 解引用到列表
my %copy = %$href;
# 取長度/鍵:
my $n = scalar @$aref;

箭頭操作符

-> 解引用嵌套結構:$ref->[0] 與 $ref->{key},逐層鏈接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my $ref = [10, 20, 30];
say $ref->[0]; # 10
my $href = {name => 'Rex', age => 5};
say $href->{name}; # Rex
# 多層嵌套:
my $matrix = [[1, 2], [3, 4]];
say $matrix->[1][0]; # 3
my $users = [
{name => 'alice', age => 30},
{name => 'bob', age => 25},
];
say $users->[1]{name}; # bob
# 函數引用調用:
my $fn = sub { "hi" };
say $fn->(); # hi
# 等價花括號寫法:
say ${$users->[0]}{name}; # alice

匿名引用

[] 匿名數組、{} 匿名哈希、sub {} 匿名函數;直接構造無需命名變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 匿名數組:
my $aref = [1, 2, 3];
my $empty = [];
# 匿名哈希:
my $href = {name => 'Rex'};
# 匿名函數:
my $fn = sub { "hello" };
# 直接嵌套構造:
my $config = {
name => 'app',
hosts => ['a.com', 'b.com'],
};
say $config->{hosts}[0]; # a.com
# 圓括號是列表、方括號是引用:
my @list = (1, 2, 3); # 列表
my $ref = [1, 2, 3]; # 引用
# 匿名引用無需先建變量再取地址

嵌套結構

數組套哈希、哈希套數組等複合結構;遍歷時逐層解引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 數組的數組(AoA):
my $grid = [[1, 2], [3, 4]];
say $grid->[0][1]; # 2
# 哈希的哈希(HoH):
my $people = {
alice => {age => 30, city => 'NY'},
bob => {age => 25, city => 'LA'},
};
say $people->{bob}{city}; # LA
# 哈希的數組(HoA):
my $grades = {
math => [90, 85],
eng => [88],
};
say $grades->{math}[0]; # 90
# 遍歷嵌套:
for my $name (keys %$people) {
say "$name: $people->{$name}{age}";
}
# 深嵌套可用 Data::Dumper 查看結構

引用傳參

傳引用避免複製大數組、可就地修改調用方數據;函數返回引用更高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @big = (1..1000);
# 傳引用求和(不復制數組):
sub total {
my $arr = shift;
my $sum = 0;
$sum += $_ for @$arr;
return $sum;
}
say total(\@big);
# 就地修改調用方數據:
sub bump {
my $arr = shift;
$_++ for @$arr;
}
bump(\@big);
say $big[0]; # 2(被修改)
# 傳多個引用:
sub merge {
my ($a, $b) = @_;
return [@$a, @$b]; # 返回匿名引用
}
my $m = merge([1, 2], [3, 4]);
say $m->[2]; # 3

bless 與對象

bless 給引用打上類標籤成為對象;ref 之後返回類名。對象本質是引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %args = @_;
return bless { %args }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫了一聲";
}
package main;
my $cat = Animal->new(name => '喵');
say $cat->speak; # 喵 叫了一聲
# 對象是打了類標籤的哈希引用:
say ref $cat; # Animal
say $cat->{name}; # 喵
# 完整 OO 見 oop 節

引用比較

引用比較地址用 ==;ref 判斷類型;引用計數管理生命週期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @a = (1, 2);
my @b = (1, 2);
my $r1 = \@a;
my $r2 = \@a; # 同目標
my $r3 = \@b; # 不同目標
# 引用用 == 比較地址:
say $r1 == $r2; # 1
say $r1 == $r3; # 0
# eq 比較字符串化形式:
say $r1 eq $r2; # 1
# ref 判斷類型:
say ref $r1; # ARRAY
say ref \5; # SCALAR
say ref undef; # ""
# 解引用前判類型:
if (ref $href eq 'HASH') {
say $href->{name};
}
# 引用計數見 mem 節

5.流程控制

if/unless、後綴式、for/foreach/while 與 next/last/redo。

if/elsif/else

if 判斷真假值;elsif 鏈式;unless 反向判斷。條件無括號要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $score = 85;
if ($score >= 90) {
say "優秀";
} elsif ($score >= 60) {
say "及格";
} else {
say "不及格";
}
# 假值判斷(0/空/undef 為假):
my $name = "Rex";
if ($name) { say "有名字" }
# unless = if not:
my $debug = 0;
unless ($debug) { say "不開調試" }
# 花括號不可省略:
# if ($x) say 1; 語法錯誤
# 表達式做條件即可:
if (-f 'data.txt') { say "是文件" }

unless 與 until

unless 是 if 的否定、until 是 while 的否定;do-until 至少執行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $debug = 0;
# unless = if not:
unless ($debug) {
say "不輸出調試信息";
}
# until = while not:
my $n = 0;
until ($n >= 3) {
say "n=$n";
$n++;
}
# 後綴形式:
say "缺省" unless defined $config;
# do-until 至少執行一次:
do {
$n--;
} until ($n == 0);
# 適用反向條件,可讀性更好
# 複雜條件仍用 if/while

後綴式控制

if/unless/for/while 可放在單語句後,是 Perl 慣用法。只修飾一條語句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $n = 120;
# 後綴修飾語句:
say "很大" if $n > 100;
die "超時" unless $done;
for my $i (1..5) {
say "第 $i 個";
}
# 後綴 for:
print "$_ " for 1..3;
my $total = 0;
$total += $_ for @nums;
# 等價前置寫法:
# if ($n > 100) { say "很大" }
# for (@nums) { $total += $_ }
# 只能修飾一條語句:
# say 1; say 2 if $c; 只 if 修飾 say 2
# 多語句用普通塊

for/foreach 循環

for 可寫 C 風格或遍歷列表;foreach 是別名。不指定變量時用 $_。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# C 風格:
for (my $i = 0; $i < 5; $i++) {
say $i;
}
# 遍歷列表:
for my $item (1..3) {
say $item;
}
# 遍歷數組:
my @names = qw(alice bob);
for my $name (@names) {
say $name;
}
# 省略變量用 $_:
for (1..3) {
say "當前: $_";
}
# foreach 完全等價:
foreach my $x (@names) {
say $x;
}
# 空列表不執行循環體

while 循環

while 條件為真即執行;讀文件用 while 逐行最慣用;do-while 先執行一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# 逐行讀文件:
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
# 讀到 EOF 返回 undef 終止循環
# 無限循環:
my $done = 0;
while (1) {
last if $done;
$done = 1;
}
# 至少執行一次:
my $x = 10;
do {
say $x;
$x--;
} while ($x > 5);
# until 反向:
my $i = 0;
until ($i > 3) {
say "i=$i";
$i++;
}

next/last/redo

next 跳過本輪、last 退出循環、redo 重做本輪;標籤控制嵌套循環。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# next 跳過本輪:
for my $i (1..10) {
next if $i % 2; # 跳過奇數
say $i;
}
# last 退出循環:
for my $i (1..10) {
last if $i > 5;
say $i;
}
# redo 重做本輪:
my $sum = 0;
my $i = 0;
BLOCK: {
$i++;
$sum += $i;
redo BLOCK if $i < 3;
}
say $sum; # 6
# 標籤控制嵌套循環:
OUTER: for my $x (1..3) {
for my $y (1..3) {
next OUTER if $y == 2;
say "$x,$y";
}
}

三目與短路

?: 條件表達式;|| 與 // 提供默認值;邏輯運算返回操作數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $n = -3;
# 三目運算:
my $sign = $n >= 0 ? "非負" : "負數";
say $sign;
# || 默認值(假則替換):
my $name;
my $shown = $name || "無名氏";
# // 定義或(僅 undef 替換):
my $port;
$port //= 8080; # undef 才賦默認
# 邏輯返回操作數:
my $a = 0 && die "不會執行";
my $b = 1 || die "不會執行";
# 嵌套三目可讀性差:
# my $g = $n>0 ? "正" : $n<0 ? "負" : "零";
# 建議拆成 if/elsif

goto 與標籤

goto 跳轉標籤(限制版);多層退出優先用帶標籤的 last。日常代碼極少用 goto。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 限制版 goto 跳標籤:
my $i = 0;
START:
$i++;
goto START if $i < 3;
say "結束: $i";
# goto 不能跳入塊內
# 優先用循環控制:
my @items = (1, -1, 2);
for my $item (@items) {
last if $item < 0;
say $item;
}
# 多層退出用標籤 + last:
OUTER: for my $a (1..3) {
for my $b (1..3) {
last OUTER if $a * $b > 4;
say "$a x $b";
}
}
# 可讀性優先,別用 goto 跳來跳去

6.函數與子程序

sub 定義、@_ 參數、返回值、閉包、原型與簽名。

函數定義

sub 定義命名子程序,最後一個表達式自動返回。調用時用括號傳參。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 定義與調用:
sub greet {
return "hello";
}
say greet(); # hello
# 帶參數:
sub add {
my ($a, $b) = @_;
return $a + $b;
}
say add(2, 3); # 5
# 最後一個表達式自動返回:
sub square { my $x = shift; $x * $x }
say square(4); # 16
# 無返回值:
sub log_msg {
say "記錄日誌";
}
# 函數名大小寫敏感
# 未定義就調用會在編譯期報錯

@_ 與參數

參數列表存於 @_;shift 取出第一個;@_ 元素是別名,就地修改會外泄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
# @_ 是參數列表:
sub first_arg {
my $x = shift; # 取出第一個
return $x;
}
# 多個參數:
sub two {
my ($a, $b) = @_; # 列表賦值
return "$a-$b";
}
say two(1, 2); # 1-2
# shift 在函數內默認操作 @_:
sub total {
my $sum = 0;
$sum += shift while @_;
return $sum;
}
say total(1, 2, 3); # 6
# @_ 是別名,修改會外泄:
sub bump_first { $_[0]++ }
my $x = 1;
bump_first($x);
say $x; # 2
# 通常先複製到詞法變量再用

返回值

返回標量或列表由上下文決定;wantarray 判斷調用上下文;返回引用避免複製。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
sub coords { return (1, 2, 3) }
# 列表上下文返回全部:
my @all = coords(); # (1,2,3)
# 標量上下文返回最後一個:
my $n = coords(); # 3
# 空返回:
sub nothing { return }
# 返回 undef:
sub missing { return undef }
# 用 wantarray 感知上下文:
sub ctx {
return wantarray ? (1, 2) : 5;
}
my @l = ctx(); # (1,2)
my $s = ctx(); # 5
# 返回引用避免複製大結構:
sub make_list {
return [1, 2, 3]; # 匿名數組引用
}
my $r = make_list();

匿名子程序

sub {} 創建函數引用,用 ->() 調用;可作回調、存數組或哈希。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
# 匿名子程序:
my $square = sub {
my $x = shift;
return $x * $x;
};
say $square->(5); # 25
# 作為參數(回調):
sub apply {
my ($fn, $val) = @_;
return $fn->($val);
}
say apply(sub { $_[0] * 2 }, 21); # 42
# 存進數組:
my @handlers = (
sub { "one" },
sub { "two" },
);
say $handlers[1]->(); # two
# 存進哈希(dispatch 表):
my %ops = (
add => sub { $_[0] + $_[1] },
mul => sub { $_[0] * $_[1] },
);
say $ops{add}->(2, 3); # 5
# 函數引用與普通引用一樣是標量

閉包

閉包捕獲詞法變量並記住狀態;每次調用工廠函數得到獨立副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 計數器閉包:
sub make_counter {
my $count = 0;
return sub {
$count++; # 記住狀態
return $count;
};
}
my $c = make_counter();
say $c->(); # 1
say $c->(); # 2
# 各自獨立狀態:
my $a = make_counter();
my $b = make_counter();
$a->(); # 1
$b->(); # 1
# 工廠函數:
sub make_greeter {
my $prefix = shift;
return sub { "$prefix: $_[0]" };
}
my $hi = make_greeter("hi");
say $hi->("Rex"); # hi: Rex
# 閉包捕獲的是變量本身而非副本

原型

原型在編譯期約束參數上下文,如 (\@) 強制數組引用。現代代碼建議用簽名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 原型約束參數:
sub myshift (\@) {
my $arr = shift;
return shift @$arr;
}
my @nums = (1, 2, 3);
my $first = myshift(@nums); # 自動轉引用
say $first; # 1
# 常見原型:
# ($) 強制標量上下文
# (\@) 強制數組引用
# (\%) 強制哈希引用
# ($@) 一個標量後跟數組
# 原型對方法調用無效:
# $obj->meth(@args) 忽略原型
# 原型可能誤導,除非寫內置風格函數
# 5.36+ 推薦用簽名替代:
sub real($a, $b) { $a + $b }

函數簽名

feature 'signatures' 提供聲明式參數(5.36+ 穩定);支持默認值與數組參數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'signatures';
no warnings 'experimental::signatures';
# 聲明式參數:
sub add($a, $b) {
return $a + $b;
}
say add(2, 3); # 5
# 默認值:
sub greet($name = "world") {
return "hello, $name";
}
say greet(); # hello, world
# 多參數 + 數組:
sub config($name, @rest) {
return ($name, @rest);
}
# 參數是詞法變量,不再碰 @_:
sub swap($x, $y) {
return ($y, $x);
}
my ($u, $v) = swap(1, 2);
# 簽名比 shift 更清晰
# 不支持命名參數,用哈希模擬

函數引用

\&sub 取函數引用,->() 或 &$ref() 調用;用於回調、調度表與高階函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 取函數引用:
sub twice { $_[0] * 2 }
my $fn = \&twice;
say $fn->(10); # 20
say &$fn(10); # 20
# 作為參數傳遞:
sub run {
my $code = shift;
$code->();
}
run(sub { say "運行" });
# 檢查是否是代碼引用:
my $cb = \&twice;
say ref $cb; # CODE
# 尾調用優化(複用調用幀):
sub loop {
return if $_[0] <= 0;
@_ = ($_[0] - 1);
goto &loop;
}
loop(100000);
# 深遞歸優先考慮 goto &sub

7.字符串

引號、插值、拼接、格式化、子串與編碼處理。

字符串字面量

單引號原樣、雙引號插值;q()/qq() 等價;相鄰字符串自動拼接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
# 單引號不插值:
my $a = '原樣 $name';
# 雙引號插值:
my $b = "hi $name";
# q/qq 運算子(任意定界符):
my $c = q(原樣 $name);
my $d = qq(插值 $name);
# 單引號內轉義 \\ 和 \':
my $e = 'it\'s ok';
# 相鄰字面量自動拼接:
my $f = 'a' 'b' 'c'; # abc
# 換行與製表符:
my $g = "第一行\n第二行\t製表";
# 引號嵌套注意轉義:
my $h = "他説 \"你好\"";

插值規則

雙引號內 $scalar 與 @array 插值;花括號限定邊界;索引/解引用也可插值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
my $name = "Rex";
my @nums = (1, 2, 3);
# 標量插值:
my $a = "你好, $name";
# 數組插值(空格分隔):
my $b = "數組: @nums"; # 數組: 1 2 3
# 花括號限定變量邊界:
my $s = "${name}s house";
# 索引插值:
my @h = qw(x y z);
my $c = "第二個: $h[1]"; # y
# 解引用插值:
my $user = {name => 'Rex'};
my $d = "名字: $user->{name}";
# 轉義:
my $e = "換行\n製表\t";
# \\ 反斜槓 \$ 字面美元
# 變量名含非單詞字符需花括號

拼接與重複

. 拼接字符串、x 重複、join 連接數組;length 求長度、reverse 反轉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 點號拼接:
my $full = "Hello" . " " . "World";
# 數字在點號上下文變字符串:
my $s = 1 . 2; # "12"
# 重複運算符:
my $line = "-" x 20; # 20 個連字符
# join 連接數組:
my @parts = ('a', 'b', 'c');
my $csv = join ",", @parts; # a,b,c
# 長度:
my $len = length "hello"; # 5
# 反轉:
my $rev = reverse "abc"; # cba
# 字符串比較:
"a" eq "a"; # 相等
"a" lt "b"; # 小於(字典序)
"abc" cmp "abd"; # 比較結果
# 拼接用 .= 追加到變量:
my $buf = "x";
$buf .= "y"; # xy

子串與查找

substr 取/改子串;index/rindex 查找位置(找不到返回 -1)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my $s = "Hello, World";
# 取子串:
my $sub = substr $s, 0, 5; # Hello
my $from = substr $s, 7; # World
my $last4 = substr $s, -5; # World(從尾部)
# 查找位置:
my $pos = index $s, "World"; # 7
my $last = rindex $s, "o"; # 8(從右找)
# 找不到返回 -1:
index $s, "zzz"; # -1
# 替換子串:
my $t = $s;
substr($t, 7, 5) = "Perl"; # Hello, Perl
# 按行拆分用 split:
my @words = split /,/, $s;
# 更復雜的提取用正則(見 regex 節)

chomp 與 split

chomp 去掉末尾換行;split 按分隔符拆成列表;join 是逆操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# chomp 去掉末尾換行:
my $line = "abc\n";
chomp $line; # "abc"
# chomp 就地修改,返回去掉的字符數
# split 按分隔符:
my @f = split /,/, "a,b,c"; # a b c
my @w = split /\s+/, "a b"; # a b
# 省略分隔符按空白拆:
my @t = split /\s+/, "a b c";
# 限制拆分個數:
my @two = split /,/, "a,b,c", 2; # a, "b,c"
# join 逆操作:
my $back = join "-", @f; # a-b-c
# 字符串轉行數組:
my @lines = split /\n/, $text;
# 解析 CSV 建議用 Text::CSV 模塊

printf/sprintf

printf 直接輸出,sprintf 返回字符串;%d/%s/%f 等格式與補零對齊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# printf 直接輸出:
printf "%s 今年 %d 歲\n", "Rex", 5;
# sprintf 返回字符串:
my $s = sprintf "%05d", 42; # 00042
# 常用格式:
# %s 字符串 %d 整數 %f 浮點
# %x 十六進制 %o 八進制 %e 科學計數
# %.2f 兩位小數 %5d 右對齊補空格
# %-5d 左對齊 %05d 補零
my $money = sprintf "%.2f", 3.14159; # 3.14
my $hex = sprintf "%x", 255; # ff
# 動態寬度:
my $w = 10;
my $padded = sprintf "%${w}s", "hi";
# 輸出到句柄:
printf STDOUT "值: %d\n", 42;
# 參數按順序匹配,多參也支持

大小寫與清洗

uc/lc/ucfirst 改大小寫;用替換正則去空白;tr/// 字符翻譯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my $s = "Hello World";
my $upper = uc $s; # HELLO WORLD
my $lower = lc $s; # hello world
my $cap = ucfirst $s; # Hello World
my $w = lcfirst "Hello"; # hello
# 去首尾空白(正則):
my $t = " padded ";
$t =~ s/^\s+//; # 去開頭
$t =~ s/\s+$//; # 去結尾
# 前綴/後綴判斷:
$s =~ /^Hello/;
$s =~ /World$/;
# 字符翻譯:
my $u = "abc";
$u =~ tr/a-z/A-Z/; # ABC
# 刪除翻譯:
my $d = "a1b2c3";
$d =~ tr/0-9//d; # abc
# 字符與碼點:
ord 'A'; # 65
chr 65; # "A"

編碼與 UTF-8

use utf8 標記源碼;encode/decode 轉碼;帶編碼層讀寫文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use utf8; # 源碼按 UTF-8 解釋
use Encode;
my $chinese = "中文";
binmode STDOUT, ':utf8'; # 輸出 UTF-8
# 編碼與解碼:
my $bytes = encode('UTF-8', $chinese); # 字節
my $back = decode('UTF-8', $bytes); # 字符
# 字符數 vs 字節數:
my $text = "你好";
length $text; # 2(字符)
length encode('UTF-8', $text); # 6(字節)
# 帶編碼層讀寫:
open my $fh, '<:utf8', 'file.txt' or die $!;
open my $out, '>:utf8', 'out.txt' or die $!;
# JSON 使用 UTF-8:
use JSON::PP;
my $j = JSON::PP->new->utf8->encode({k => "中文"});

8.集合與數據結構

數組/哈希操作、切片、map/grep、排序與 List::Util。

數組增刪改

push/pop/shift/unshift 四個端點操作;splice 中間插入刪除;reverse/sort。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3);
# 棧操作:
push @nums, 4; # 末尾加
my $top = pop @nums; # 末尾彈
unshift @nums, 0; # 開頭加
my $head = shift @nums; # 開頭彈
# 中間插入/刪除:
splice @nums, 1, 0, 99; # 下標1 插入 99
splice @nums, 0, 1; # 刪除下標0
# 反轉與排序:
my @rev = reverse @nums;
my @sorted = sort @nums;
# 合併:
my @all = (@nums, (4, 5));
# 末尾追加多個:
push @nums, 6, 7;
# 數組長度與最後元素:
my $len = scalar @nums;
my $end = $nums[-1];

切片

@arr[...] 取數組切片、%hash{...} 取哈希切片;切片可整體賦值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @arr = qw(a b c d e);
# 數組切片:
my @mid = @arr[1..3]; # b c d
my @sel = @arr[0, 2, 4]; # a c e
# 切片可賦值:
@arr[1, 2] = qw(X Y);
my %h = (alice => 30, bob => 25, carol => 28);
# 哈希切片(取多個鍵的值):
my @ages = @h{qw(alice bob)}; # 30 25
# 哈希切片賦值:
@h{qw(dan eve)} = (22, 35);
# 整個數組/哈希:
my @all = @arr;
# 引用上的切片:
my $r = \@arr;
my @sub = @$r[1..2];
# 切片在標量上下文返回數量:
my $n = @arr[1..3]; # 3

列表運算

sum/min/max 來自 List::Util;grep 去重、範圍取值、列表解構。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use List::Util qw(sum min max);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $min = min @nums; # 1
my $max = max @nums; # 5
# 去重(保持首次出現順序):
my %seen;
my @uniq = grep { !$seen{$_}++ } @nums;
# 合併:
my @a = (1, 2);
my @b = (3, 4);
my @ab = (@a, @b);
# 列表解構:
my ($x, $y) = @nums; # 3, 1
my ($head, @rest) = @nums;
# 排序後取前三:
my @top3 = (sort { $b <=> $a } @nums)[0..2];
# 忽略返回值時注意臨時數組開銷

map 與 grep

grep 過濾、map 變換,都按 $_ 迭代。標量上下文 grep 返回命中數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
my @nums = (1, 2, 3, 4, 5);
# grep 過濾:
my @big = grep { $_ > 2 } @nums; # 3 4 5
my @even = grep { $_ % 2 == 0 } @nums; # 2 4
# map 變換:
my @sq = map { $_ * $_ } @nums; # 1 4 9 16 25
my @labels = map { "第 $_ 個" } @nums;
# grep 標量上下文返回命中數:
my $count = grep { $_ > 3 } @nums; # 2
# map 生成哈希:
my %square = map { $_ => $_ * $_ } 1..5;
# 組合使用:
my @big_sq = map { $_ * $_ }
grep { $_ > 2 } @nums; # 9 16 25
# grep/map 不改原數組
# 映射結果未使用會白算

哈希遍歷

keys/values 取鍵值列表,each 鍵值對遍歷;exists/delete 管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
my %h = (a => 1, b => 2, c => 3);
# 鍵 / 值列表:
my @keys = keys %h;
my @values = values %h;
# 鍵值對遍歷(each 消耗游標):
while (my ($k, $v) = each %h) {
say "$k=$v";
}
# 每次 each 調用取下一對
# 多次 each 嵌套易錯,單層用 each
# 存在與刪除:
exists $h{a}; # 1
delete $h{b};
# 清空:
%h = ();
# 合併:
my %m = (d => 4, %h);
# 鍵值反轉:
my %inv = reverse %m;
# 計數常用 each + ++:
$count{$_}++ for @words;

哈希慣用法

哈希做計數、去重、集合、緩存與分組。遍歷中的增刪要小心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use feature 'say';
# 計數:
my @words = qw(a b a c b a);
my %count;
$count{$_}++ for @words; # a=>3 b=>2 c=>1
# 去重:
my %seen;
my @uniq = grep { !$seen{$_}++ } @words;
# 集合判斷:
my %set = map { $_ => 1 } qw(a b c);
say "存在" if $set{a};
# 緩存:
my %cache;
sub expensive {
my $key = shift;
return $cache{$key} if exists $cache{$key};
$cache{$key} = $key * $key;
return $cache{$key};
}
# 分組(按首字母):
my %groups;
for my $word (@words) {
my $first = substr $word, 0, 1;
push @{ $groups{$first} }, $word;
}
# 數組值哈希存引用,讀時需解引用

排序

sort 默認字符串序;自定義比較塊用 <=> 數值、cmp 字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
my @nums = (5, 3, 8, 1);
# 默認字符串排序:
my @s = sort @nums; # 1 3 5 8
# 數值升/降序:
my @n = sort { $a <=> $b } @nums; # 升序
my @d = sort { $b <=> $a } @nums; # 降序
# 字符串比較:
my @w = sort { $a cmp $b } qw(banana apple);
# 按長度:
my @len = sort { length $a <=> length $b }
@words;
# 穩定排序(次要鍵):
my @st = sort {
length $a <=> length $b || $a cmp $b
} @words;
# 哈希按鍵排序遍歷:
my %h = (b => 2, a => 1);
for my $k (sort keys %h) {
say "$k=$h{$k}";
}
# 比較塊裏 $a/$b 是元素,不是索引

List::Util

sum/sum0/min/max/first/reduce/any/all/shuffle。sum0 空列表返回 0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
use List::Util qw(
sum sum0 min max first reduce shuffle any all
);
my @nums = (3, 1, 4, 1, 5);
my $sum = sum @nums; # 14
my $sum0 = sum0 @nums; # 空列表得 0
my $min = min @nums; # 1
my $max = max @nums; # 5
# first 首個滿足:
my $f = first { $_ > 3 } @nums; # 4
# any / all:
my $has = any { $_ > 4 } @nums; # true
my $all = all { $_ > 0 } @nums; # true
# reduce 歸約:
my $prod = reduce { $a * $b } @nums;
# shuffle 洗牌:
my @mix = shuffle @nums;
# 跨列表求最值:
min @a, @b;
# 注意:sum/min 對空列表返回 undef

9.內存與性能

引用計數、循環引用、弱引用、自動活體化與性能基準。

引用計數

Perl 用引用計數自動回收內存,無需手動 free。計數歸零即釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
# 引用計數自動管理:
my $ref = [1, 2, 3]; # 計數 1
my $copy = $ref; # 計數 2
undef $copy; # 計數 1
undef $ref; # 計數 0,釋放
# 詞法變量離開作用域即回收:
{
my $tmp = "數據";
} # $tmp 在這裏釋放
# 無手動 malloc/free:
# 只管創建,回收交給引擎
# 與 C/Rust 的顯式管理不同
# 內存峯值觀察:
# 大數組處理完可 undef 提前釋放
# 或改用流式處理,別整塊載入
# 循環引用無法靠計數回收見下節

循環引用

對象互相引用使計數永不歸零導致泄漏;用弱引用或手動破環解決。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 循環引用導致泄漏:
my $a = {};
my $b = {};
$a->{other} = $b; # a 引用 b
$b->{other} = $a; # b 引用 a
# 計數互相保持,兩塊內存永不釋放
# 自引用同樣泄漏:
my $node = {};
$node->{self} = $node;
# 解決 1:手動破環:
$a->{other} = undef;
# 解決 2:弱引用:
use Scalar::Util 'weaken';
my $wa = {};
my $wb = {};
$wa->{other} = $wb;
weaken $wa->{other}; # 不強引用
# $wb 可被正常回收
# 檢測:Devel::Leak / valgrind
# 數據結構儘量做成 DAG(無環)

弱引用

Scalar::Util::weaken 讓引用不增計數;目標回收後弱引用變 undef。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
use Scalar::Util qw(weaken isweak);
# 強引用 + 弱引用:
my $data = {name => 'Rex'};
my $strong = $data; # 強引用
my $weak = $data;
weaken $weak; # 轉弱引用
say isweak($weak); # 1
# 釋放強引用:
undef $data;
undef $strong;
# 弱引用指向已釋放內存:
say defined $weak; # 0(已變 undef)
# 緩存不阻止回收:
my %cache;
my $obj = {id => 1};
$cache{1} = $obj;
weaken $cache{1};
# 使用前判空:
if (defined $cache{1}) {
say "命中緩存";
}
# 弱引用常用於觀察者/緩存場景

作用域釋放

詞法變量與句柄在離開作用域時自動釋放/關閉;undef 可提前釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 函數返回後局部變量釋放:
sub work {
my $big = "x" x 1000000; # 大字符串
process($big);
return 1;
# $big 在返回時釋放
}
# 用塊提前結束生命週期:
{
my $tmp = load_data();
process($tmp);
} # $tmp 塊結束即釋放
# 文件句柄自動關閉:
sub read_all {
open my $fh, '<', 'data.txt' or die $!;
local $/;
return <$fh>; # 返回時 $fh 關閉
}
# 主動釋放:
undef @big_array; # 立即釋放
# 引用的數據在引用存活期間保留:
my $keep = [1, 2, 3];
my $holder = {list => $keep}; # $keep 活着

自動活體化

給未定義引用賦值時自動創建容器;寫很方便,讀嵌套時可能誤創建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 寫時自動創建:
my $hash;
$hash->{a} = 1; # 自動建哈希引用
say $hash->{a}; # 1
my $arr;
$arr->[0] = "x"; # 自動建數組引用
# 多層自動創建:
my $deep;
$deep->{a}[0]{b} = 1; # 逐層創建
# 讀不存在的鍵返回 undef:
my %h;
say defined $h{missing} ? "有" : "無";
# 讀嵌套也可能創建中間層:
my $outer;
my $v = $outer->{x}{y}; # 創建了 {x}
# 用短路判空避免:
if ($outer && $outer->{x}) {
say "有值";
}
# 需要徹底關閉時:
# use no autovivification;

分配與提速

預分配數組、預編譯正則、少用插值拼接;先測後優化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 預分配數組容量:
my @nums;
$#nums = 9999; # 預分配
for my $i (0..9999) {
$nums[$i] = $i;
}
# 正則預編譯複用:
my $re = qr/\d{4}-\d{2}-\d{2}/;
for my $line (@lines) {
$line =~ $re; # 複用編譯結果
}
# 拼接優先 join:
my $big = join '', @parts;
# 大量 .= 拼接仍偏慢:
my $s = "";
$s .= "x" for 1..1000;
# 純文本用單引號:
print '純文本';
# 哈希做 O(1) 查找:
my %lookup = map { $_ => 1 } @list;
# 分析用 Devel::NYTProf
# 先測再優化,別盲目微調

內存度量

Devel::Size 測量結構佔用;觀察內存峯值;大文件流式處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 測量結構內存:
use Devel::Size qw(total_size);
my $struct = {
name => 'Rex',
tags => [qw(dev tools)],
};
say total_size($struct); # 字節數
# 單個變量:
Devel::Size::size($struct);
# 逐塊定位大對象:
# 先 dump 頂層再逐塊比較
# 進程內存監控(Linux):
# 讀 /proc/$$/status 的 VmRSS
# Windows 用任務管理器觀察
# 大文件流式處理:
open my $fh, '<', 'big.log' or die $!;
while (my $line = <$fh>) {
process($line); # 不整塊載入
}
# 參考:perldoc Devel::Size

性能基準

Benchmark 模塊對比實現;Time::HiRes 高精度計時。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
use Benchmark qw(cmpthese);
my @data = (1..1000);
# 對比兩種寫法:
cmpthese(1000, {
join_way => sub {
my $s = join ',', @data;
},
concat_way => sub {
my $s = "";
$s .= "$_" for @data;
},
});
# 輸出每秒執行次數與相對速度
# 秒級計時:
my $t0 = time;
work();
my $elapsed = time - $t0;
say "耗時 $elapsed 秒";
# 高精度計時:
use Time::HiRes qw(time);
my $t1 = time;
work();
my $ms = (time - $t1) * 1000;
say "耗時 $ms 毫秒";
# 熱點剖析用 Devel::NYTProf

10.面向對象

bless 引用、方法、繼承、訪問器、運算符重載與 Moose/Moo。

bless 構造

bless 把引用打上類標籤成為對象;對象本質是帶類名的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
package Person;
sub new {
my $class = shift;
my $self = { name => '未知' };
return bless $self, $class;
}
# 對象是打了標籤的引用:
my $p = Person->new();
say ref $p; # Person
# 類名默認當前包:
sub new_short {
my $self = {x => 1};
return bless $self; # 類為當前包
}
# 對象本質是哈希引用:
$p->{name} = 'Rex'; # 直接改字段
# 推薦封裝在方法裏(見訪問器)
# 其他類型引用也可 bless:
# bless [], 'Stack'; bless \$x, 'Ref'

方法與調用

-> 左側是對象、右側是方法;方法第一個參數是 $self。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Counter;
sub new {
my $class = shift;
return bless { count => 0 }, $class;
}
sub inc {
my $self = shift;
$self->{count}++;
return $self->{count};
}
sub value {
my $self = shift;
return $self->{count};
}
package main;
my $c = Counter->new(); # 構造調用
$c->inc(); # 方法調用
$c->inc();
say $c->value(); # 2
# 方法第一個參數是對象:
# 等價於函數式調用:
Counter::value($c); # 2
# 方法繼承自類及父類
# -> 可鏈式:$c->inc()->inc()

繼承

use parent 聲明父類;子類覆蓋同名方法;父類方法可複用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package Animal;
sub new {
my $class = shift;
my %a = @_;
return bless { name => $a{name} }, $class;
}
sub speak {
my $self = shift;
return "$self->{name} 叫";
}
sub eat {
my ($self, $food) = @_;
return "$self->{name} 吃 $food";
}
package Dog;
use parent -norequire, 'Animal';
sub speak { # 覆蓋父方法
my $self = shift;
return "$self->{name} 汪汪";
}
package main;
my $d = Dog->new(name => '旺財');
say $d->speak; # 旺財 汪汪
say $d->eat('骨頭'); # 繼承的 eat
# 查找方法:對象類 → 父類鏈
# 多繼承用 @ISA 或 use parent

構造函數

new 接收類名與參數,返回 bless 對象;可提供多種構造方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use feature 'say';
package Point;
sub new {
my ($class, %args) = @_;
my $self = {
x => $args{x} // 0,
y => $args{y} // 0,
};
return bless $self, $class;
}
sub coords {
my $self = shift;
return ($self->{x}, $self->{y});
}
# 變體:從列表構造:
sub from_list {
my ($class, $pair) = @_;
return $class->new(x => $pair->[0],
y => $pair->[1]);
}
package main;
my $p = Point->new(x => 3, y => 4);
my ($x, $y) = $p->coords;
say "$x,$y"; # 3,4
# 字段默認值與校驗放在 new 裏
# 命名參數用胖箭頭更清晰

封裝與訪問器

把字段訪問封裝成方法,寫訪問器可加校驗;避免外部直接改字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
package Account;
sub new {
my ($class, $name) = @_;
return bless {
name => $name,
money => 0,
}, $class;
}
# 讀訪問器:
sub name { my $s = shift; return $s->{name} }
sub money { my $s = shift; return $s->{money} }
# 寫訪問器(帶校驗):
sub deposit {
my ($self, $amt) = @_;
die "金額必須為正" if $amt <= 0;
$self->{money} += $amt;
}
package main;
my $acc = Account->new('alice');
$acc->deposit(100);
say $acc->name; # alice
say $acc->money; # 100
# 語義校驗集中在方法內
# 直接改 $acc->{money} 會繞過校驗

運算符重載

use overload 定義 +、"" 等運算符;讓對象像數值/字符串一樣參與運算。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
package Money;
use overload (
'""' => 'as_string', # 字符串化
'+' => 'add', # 加法
'fallback' => 1,
);
sub new {
my ($class, $cents) = @_;
return bless { cents => $cents }, $class;
}
sub as_string {
my $self = shift;
return sprintf %.2f", $self->{cents} / 100;
}
sub add {
my ($a, $b) = @_;
return Money->new($a->{cents} + $b->{cents});
}
package main;
my $m1 = Money->new(199);
my $m2 = Money->new(1);
say $m1 + $m2; # ¥2.00
say "$m1"; # ¥1.99
# 可重載:+ - * / <=> cmp 等
# 注意真值/引用比較的語義

Moo/Moose

Moo 輕量對象系統:has 聲明字段、自動生成訪問器、extends 繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# Moo(輕量,推薦小項目):
package Employee;
use Moo;
has name => (is => 'ro', required => 1);
has salary => (is => 'rw', default => 0);
sub annual {
my $self = shift;
return $self->salary * 12;
}
package Manager;
use Moo;
extends 'Employee';
has team_size => (is => 'ro', default => 0);
package main;
my $e = Employee->new(name => 'Rex');
$e->salary(5000);
say $e->name; # Rex
say $e->annual; # 60000
# Moo 自動生成訪問器
# is => 'ro' 只讀,'rw' 讀寫
# Moose 是完整版(類型/角色/元編程)
# 腳本用 Moo,大型項目用 Moose

角色與組合

Moo::Role 定義可複用方法集合,with 引入;組合優於繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# 角色是可複用方法集合:
package Walkable;
use Moo::Role;
sub walk { "行走中" }
package Car;
use Moo;
with 'Walkable'; # 引入角色
sub drive { "駕駛中" }
package main;
my $car = Car->new;
say $car->walk; # 行走中
say $car->drive; # 駕駛中
# 多個角色組合:
# with 'Walkable', 'Drivable';
# 同名方法按 with 順序後者覆蓋
# 角色避免鑽石繼承問題
# Moose 的 roles 思路相同
# 純手寫可用哈希存子程序模擬
# 共享行為優先考慮角色而非繼承

11.錯誤處理

die/warn、eval 捕獲、Try::Tiny、Carp 與自定義異常。

die 與 warn

warn 打印警告不退出;die 打印並終止;die 可拋字符串或對象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# warn 打印到 STDERR,不退出:
warn "警告:配置缺失\n";
# die 打印並退出(非 0 狀態):
# die "致命錯誤\n";
# die 後不會執行後續代碼
# die 在 eval 中可被捕獲:
eval {
die "可捕獲的錯誤\n";
};
say "捕獲: $@" if $@;
# die 可帶對象/引用:
die { code => 500, msg => "server error" };
# 默認退出碼:
# die 用 $! 或 255;exit N 自定義
# 常見:$! 記錄最近系統錯誤
# 生產代碼:die 前帶上 $! 便於排錯

eval 捕獲

eval BLOCK 捕獲異常,出錯置 $@;成功返回塊內最後表達式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# eval BLOCK 捕獲異常:
eval {
die "boom";
};
if ($@) {
say "捕獲: $@";
}
# 成功時 $@ 為空
# eval 返回值:
my $result = eval {
compute(); # 若拋錯返回 undef
};
# 出錯返回 undef
# 嵌套 eval 保存 $@:
my $outer_err;
eval {
eval { die "內層" };
$outer_err = $@;
};
# 字符串 eval 有安全問題:
# eval $code; # 不推薦
# 只用 eval BLOCK 做異常捕獲
# 語法錯誤在編譯期已暴露

$@ 處理

$@ 保存最近錯誤;可匹配字符串或檢查對象類型;區分 undef 與異常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# $@ 包含最近錯誤:
eval { die "出錯啦" };
my $err = $@; # 保存後再用
say $err if $err;
# 按內容匹配:
eval { die "timeout" };
if ($@ && $@ =~ /timeout/) {
say "超時處理";
}
# die 對象時 $@ 是對象:
eval {
die bless {msg => 'x'}, 'MyError';
};
if (ref $@ eq 'MyError') {
say $@->{msg};
}
# 區分結果 undef 與異常:
my $ok = eval { compute() };
if ($ok) { say "成功" }
# $@ 是全局變量,勿跨調用依賴
# 現代寫法用 Try::Tiny 隔離 $_

Try::Tiny

try/catch/finally 結構清晰,$_ 承載錯誤;解決 $@ 競態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use Try::Tiny;
# try/catch:
try {
die "boom";
} catch {
say "捕獲: $_"; # 錯誤在 $_
};
# 返回值:
my $result = try {
compute();
} catch {
warn "失敗: $_";
undef; # 出錯返回 undef
};
# 最終清理:
try {
risky();
} catch {
say "出錯";
} finally {
cleanup(); # 總會執行
};
# 保留上下文:
my @rows = try { fetch_rows() };
# Try::Tiny 捕獲用局部 $_
# 避免 $@ 被嵌套 eval 覆蓋
# 核心模塊 JSON 等錯誤也可捕獲

Carp 報錯

croak/carp 報調用者位置;confess/cluck 帶堆棧。庫代碼更友好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use Carp qw(carp croak confess);
# croak 報調用者位置:
sub open_data {
croak "無法打開" unless -e "data.txt";
}
open_data(); # 錯誤指向調用行
# carp 警告(報調用者):
sub deprecated { carp "已廢棄" }
# confess 打印完整堆棧:
sub inner { confess "內部錯誤" }
sub outer { inner() }
# 區別:
# die/warn 報當前行
# croak/carp 報調用者行
# confess/cluck 報調用者 + 堆棧
# 庫代碼用 croak 使用者更易定位

自定義異常

die 對象並用 ref/isa 判斷;重載 "" 讓字符串化友好。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use strict;
use warnings;
use feature 'say';
package MyError;
sub new {
my ($class, %args) = @_;
return bless { %args }, $class;
}
sub as_string {
my $self = shift;
return "$self->{code}: $self->{msg}";
}
use overload ('""' => 'as_string');
package main;
sub risky_thing {
my $ok = shift;
die MyError->new(code => 500, msg => "失敗")
unless $ok;
}
# 拋出與捕獲:
eval { risky_thing(0) };
if (ref $@ && $@->isa('MyError')) {
say "錯誤碼: $@->{code}"; # 500
}
# 簡化方案:
# use Exception::Class;
# 或 Ouch 模塊一鍵拋錯
# 對象異常便於攜帶結構化信息

autodie

autodie 讓 open 等系統調用失敗自動 die,省去手寫 or die。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use autodie;
# 打開失敗自動 die:
open my $fh, '>', 'out.txt';
# 不再需要 or die $!
# 覆蓋常用系統調用:
# open close chdir system exec
# chmod mkdir rmdir rename unlink
# 指定範圍:
use autodie qw(open);
# 關閉自動檢查:
no autodie;
# 結合 eval 捕獲:
eval {
open my $f, '<', 'missing.txt';
};
if ($@) { say "打開失敗" }
# 搭配 Try::Tiny 更佳:
use Try::Tiny;
try {
open my $f, '<', 'x.txt';
} catch {
say "無法打開: $_";
};
# autodie 隨現代 Perl 發佈(2.30+)

錯誤處理模式

or die 慣用法、提前校驗、失敗即早返回;頂層 die、庫內部返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use strict;
use warnings;
use feature 'say';
# or die 慣用法:
open my $fh, '<', 'data.txt'
or die "打開失敗: $!";
# 提前校驗參數:
sub divide {
my ($a, $b) = @_;
die "除數為零" unless $b;
return $a / $b;
}
# 返回狀態而非拋錯:
sub parse {
my $s = shift;
return $s =~ /^\d+$/ ? $s + 0 : undef;
}
my $n = parse("12");
if (defined $n) {
say "數字: $n";
} else {
say "非法輸入";
}
# 失敗即早返回:
sub load_config {
my $path = shift;
return undef unless -f $path;
open my $f, '<', $path or return undef;
local $/;
return <$f>;
}
# 頂層錯誤用 die,庫內部多用返回

12.文件 I/O

open 讀寫、鑽石操作符、slurp、編碼、路徑與 JSON。

打開與讀取

三參數 open 讀取文件,逐行 while 最省內存;-e/-f 判斷文件屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 三參數 open(推薦):
open my $fh, '<', 'data.txt' or die $!;
while (my $line = <$fh>) {
chomp $line;
say $line;
}
close $fh;
# 讀全部到數組:
open my $f2, '<', 'data.txt' or die $!;
my @lines = <$f2>;
close $f2;
# 文件屬性判斷:
if (-e 'data.txt') { say "存在" }
-f 'data.txt'; # 是普通文件
-d 'dir'; # 是目錄
-r 'data.txt'; # 可讀
# 文件名含特殊字符用三參數 open 安全
# 逐行處理比整塊讀更省內存

寫入與追加

> 覆蓋寫、>> 追加寫;print/printf 寫句柄;umask 控制權限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 覆蓋寫入:
open my $out, '>', 'out.txt' or die $!;
print $out "hello\n";
close $out;
# 追加寫入:
open my $log, '>>', 'app.log' or die $!;
print $log "time=2026 msg=start\n";
close $log;
# 格式化寫入:
printf $out "%05d\n", 42;
# 一次性寫入多行:
open my $f, '>', 'data.txt' or die $!;
print $f @lines;
close $f;
# 默認權限 0666 & ~umask:
umask 022;
# 自動創建:> 和 >> 不存在時創建
# 二進制數據先 binmode

鑽石操作符

<> 讀參數文件,無參數讀 STDIN;$. 是累計行號,$ARGV 是當前文件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 鑽石操作符讀取參數文件:
while (<>) {
chomp;
say "讀到: $_";
}
# 運行:
# perl script.pl a.txt b.txt
# 無參數時讀 STDIN:
# echo hi | perl script.pl
# 指定句柄的鑽石:
open my $fh, '<', 'data.txt' or die $!;
while (<$fh>) {
print; # 默認輸出 $_
}
# $. 跨文件累計行號:
while (<>) {
say "第 $. 行: $_";
}
# $ARGV 當前文件名:
print "文件: $ARGV\n" if eof;
# 適合一行行處理的小工具

讀取整個文件

local $/ 置 undef 一次讀完;File::Slurper 提供 read_text 便捷函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use feature 'say';
# 原生 slurp:
open my $fh, '<', 'data.txt' or die $!;
local $/; # 分隔符置 undef
my $content = <$fh>;
close $fh;
# File::Slurper(便捷):
use File::Slurper 'read_text';
my $text = read_text('data.txt');
# 一次性寫文件:
use File::Slurper 'write_text';
write_text('out.txt', "hello\n");
# 讀二進制:
# read_binary / write_binary
# 大文件勿用 slurp:
# 會佔滿內存,用逐行循環
# 需要處理編碼時配合 decode

binmode 與編碼

binmode 二進制安全;<:utf8 層讀寫 UTF-8;raw 去掉編碼層。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 二進制安全寫入:
open my $fh, '>', 'img.bin' or die $!;
binmode $fh;
print $fh pack("C*", 0..255);
close $fh;
# 帶編碼層打開:
open my $in, '<:encoding(UTF-8)', 'data.txt'
or die $!;
# utf8 層簡寫:
open my $in2, '<:utf8', 'data.txt' or die $!;
# 輸出 UTF-8:
binmode STDOUT, ':utf8';
# 去掉編碼層:
binmode $fh, ':raw';
# 讀入後轉碼:
use Encode;
while (my $line = <$in>) {
print encode('UTF-8', $line);
}
# 編碼層處理字節級細節
# 混合編碼文件手動轉碼

標準句柄

STDIN/STDOUT/STDERR;複製句柄、判斷終端、立即刷新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 標準句柄:
print STDOUT "標準輸出\n";
print STDERR "標準錯誤\n";
my $line = <STDIN>;
# 複製句柄:
open my $copy, '>&', STDOUT;
print $copy "副本輸出\n";
# 判斷是否終端:
my $tty = -t STDOUT;
say $tty ? "終端" : "管道";
# 立即刷新(不緩衝):
$| = 1; # 每次 print 即寫
# 讀固定行數:
for (1..3) {
my $l = <STDIN>;
last unless defined $l;
}
# 關閉失敗檢查:
close $copy or die "關閉失敗: $!";
# 重定向由 shell 處理:
# perl script.pl > out.txt 2>&1

路徑操作

Cwd 當前目錄、File::Spec 跨平台拼接、glob 匹配文件、File::Find 遞歸。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
use Cwd;
my $cwd = getcwd();
say $cwd;
use File::Spec;
my $path = File::Spec->catfile('data', 'sub', 'f.txt');
use File::Basename;
my ($name, $dir, $ext) = fileparse(
'data/app.pl', qr/\.[^.]*/);
# 絕對路徑:
my $abs = File::Spec->rel2abs('data.txt');
# 匹配文件:
for my $f (glob 'data/*.txt') {
say $f;
}
# 遞歸遍歷:
use File::Find;
find(sub { say $File::Find::name }, '.');
# 拼接路徑用 catfile,勿拼字符串

JSON 處理

JSON::PP 是核心模塊:encode/decode、美化、UTF-8。生產可用 JSON::XS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use feature 'say';
# JSON::PP 核心模塊(5.14+):
use JSON::PP;
my $json = JSON::PP->new->utf8->pretty;
# 編碼:
my $data = { name => 'Rex', tags => ['dev'] };
my $str = $json->encode($data);
# 解碼:
my $back = $json->decode($str);
say $back->{name};
# 便捷函數:
use JSON::PP qw(encode_json decode_json);
my $s = encode_json({a => 1}); # {"a":1}
my $h = decode_json('{"a":1}');
say $h->{a}; # 1
# 解碼失敗處理:
eval { decode_json("bad json") };
say "解析失敗" if $@;
# 性能敏感用 JSON::XS
# 結構體必須是純 Perl 引用

13.常見誤區

Perl 日常開發最容易踩的坑與正確寫法。

忘記 use strict

不啓用 strict 時拼寫錯誤靜默、變量自動全局化。strict 是安全底線。

1
2
3
4
5
6
7
8
9
10
11
# BAD:不啓用嚴格檢查,拼寫錯誤靜默
$foo = 1; # 拼錯成 $f00 很難發現
@array = (1, 2); # 無聲明自動全局化
# GOOD:use strict 強制聲明
use strict;
use warnings;
my $foo = 1;
# 拼錯在編譯期報錯
# strict 三面:vars refs subs
# 運行時問題交給 warnings

上下文混淆

標量與列表上下文結果不同:數組在標量上下文是長度。scalar 顯式表達。

1
2
3
4
5
6
7
8
9
10
11
12
# BAD:忽略標量/列表上下文差異
my @arr = (1, 2, 3);
my $n = @arr; # 3 數組長度
my $s = @arr; # 還是 3
# GOOD:顯式表達意圖
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 顯式求長度
my @copy = @arr2; # 列表上下文複製
my $last = $arr2[-1]; # 取最後元素
# 期望列表用 (),期望標量用 $s
# scalar 強制標量上下文

忘記 chomp

<STDIN> 帶回換行,忘 chomp 會導致字符串比較永遠失敗。

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:忘記 chomp,比較永遠失敗
my $line = <STDIN>; # 末尾帶換行
if ($line eq "quit") { say "不會到這" }
# GOOD:先 chomp 再比較
my $line2 = <STDIN>;
chomp $line2;
if ($line2 eq "quit") { say "退出" }
# chomp 就地修改 $_,返回去掉的字符數
# 數字輸入也建議先 chomp:
my $num = <STDIN>;
chomp $num;
my $value = $num + 0;

比較運算符混用

== 數值比較、eq 字符串比較。用錯會把 "10" 和 "010" 判為相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:用 == 比較字符串,數值化後誤判
my $a = "10";
my $b = "010";
if ($a == $b) { say "誤判相等" } # 都是 10
# GOOD:字符串用 eq
my $a2 = "10";
my $b2 = "010";
if ($a2 eq $b2) { say "不同字符串" }
# 數值比較用 ==:
if (10 == 10.0) { say "數值相等" }
# 運算符對照:
# == != < > 數值;eq ne lt gt 字符串
# 混用導致難查 bug

哈希遍歷無序

哈希內部順序不定,依賴遍歷順序會隨機出錯。需要順序就排序鍵。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# BAD:依賴哈希遍歷順序
my %h = (b => 1, a => 2);
for my $k (keys %h) {
print "$k=$h{$k}\n"; # 順序隨機
}
# GOOD:按鍵排序遍歷
my %h2 = (b => 1, a => 2);
for my $k (sort keys %h2) {
print "$k=$h2{$k}\n"; # a 先 b 後
}
# 哈希內部順序不定,勿依賴
# 需要保序用數組存鍵:
my @order = qw(a b c);
for my $k (@order) { print "$k=$h2{$k}\n" }

讀取觸發自動活體化

讀嵌套結構時中間引用被自動創建,內存悄悄增長。短路判空可避免。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:讀取嵌套時意外創建結構
my $config;
if ($config->{db}{host}) {
say "有配置";
}
# 讀操作把 $config->{db} 自動建成了引用!
# GOOD:短路判空
my $config2;
if ($config2 && $config2->{db}{host}) {
say "有配置";
}
# $config2 為 undef 時右側不執行
# 不會創建多餘結構
# 寫操作需要自動活體化(方便)
# 讀操作先判 defined / exists
# 可用 no autovivification 全關

local 與 my 混淆

local 臨時覆蓋全局變量(塊外恢復),my 才是詞法局部變量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# BAD:把 local 當局部變量用
sub work {
local $temp = 5; # 改的是全局變量
}
# GOOD:普通臨時變量用 my
sub work2 {
my $temp = 5; # 詞法變量
return $temp;
}
# local 的用途是臨時改特殊變量:
{
local $/; # 臨時置 undef 分隔符
my $all = <$fh>; # 整個文件一次讀完
}
# local 也用於局部化 $! $@
# 不要用 local 偽裝局部變量

open 寫法過時

雙參數 open + 裸字句柄污染符號表且不安全。用三參數 + 詞法句柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# BAD:雙參數 open + 裸字句柄
open FH, ">out.txt"; # 污染符號表、模式不安全
# GOOD:三參數 open + 詞法句柄
open my $fh, '>', 'out.txt' or die $!;
print $fh "數據\n";
close $fh;
# 讀取同理:
open my $in, '<', $user_file or die $!;
# 三參數區分模式與文件名
# 文件名以 > 開頭不會誤判:
open my $f, '<', ">weird_name.txt";
# 詞法句柄自動關閉,無泄漏

數組標量上下文

數組在標量上下文返回長度、列表返回尾項。取長度用 scalar。

1
2
3
4
5
6
7
8
9
10
11
12
13
# BAD:以為數組標量上下文返回首元素
my @arr = (1, 2, 3);
my $x = @arr; # 3(長度!)
my $y = (1, 2, 3); # 3(列表尾項)
# GOOD:明確上下文
my @arr2 = (1, 2, 3);
my $len = scalar @arr2; # 長度 3
my ($first, @rest) = @arr2; # 1, (2,3)
my $last = $arr2[-1]; # 3
# 逗號表達式標量上下文取最後一項
# 需要元素數用 scalar 顯式
# 需要拷貝用 @copy = @arr

14.多線程

threads 創建線程、threads::shared 共享數據、鎖與隊列同步。

創建線程

threads->create 啓動新線程;join 等待結束並取回返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use threads;
# 創建線程:
my @threads;
for my $i (1..3) {
push @threads, threads->create(sub {
say "線程 $i 運行中";
return $i * 10;
});
}
# 等待並取回結果:
for my $t (@threads) {
my $result = $t->join();
say "結果: $result";
}
# join 會阻塞直到線程結束
# 線程退出後對象釋放

join 與錯誤

join 返回線程結果;線程內 die 會傳播到主線程,可用 eval 捕獲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use threads;
# join 取回返回與錯誤:
my $t = threads->create(sub {
die "線程內錯誤\n" if rand > 0.5;
return "成功";
});
my $result = $t->join();
if ($result) {
say "返回: $result";
}
# 線程內 die 會導致主線程 die
# 想在 join 捕獲用 eval:
my $t2 = threads->create(sub {
die "boom\n";
});
eval { $t2->join() };
say "捕獲: $@" if $@;
# 未 join 的線程會泄漏
# detach 讓線程結束時自清理

共享數據

threads::shared 的 :shared 變量跨線程共享;普通 my 變量是各線程副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use strict;
use warnings;
use threads;
use threads::shared;
# 聲明共享變量:
my $count :shared = 0;
my @list :shared = ();
my %seen :shared = ();
# 共享哈希的嵌套鍵值也要共享:
my %stats :shared;
# 多線程同時 ++ 是原子的:
for (1..5) {
threads->create(sub { $count++ })->join();
}
say "count = $count"; # 5
# 共享數組/哈希才能跨線程讀寫
# 普通 my 變量每個線程獨立副本

鎖與信號量

lock 保護臨界區;Thread::Semaphore 限流。鎖在作用域結束自動釋放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use threads;
use threads::shared;
use Thread::Semaphore;
my $counter :shared = 0;
my $lock :shared;
# lock 保護臨界區:
sub increment {
lock $lock; # 其他線程等待
$counter++;
# 作用域結束自動釋放
}
# 信號量限流:
my $sem = Thread::Semaphore->new(2);
sub worker {
$sem->down(); # 獲取一個許可
do_work();
$sem->up(); # 釋放
}
# lock 在塊結束自動釋放
# 死鎖風險:嵌套 lock 順序要一致

線程隊列

Thread::Queue 是線程安全隊列,dequeue 空時阻塞,適合生產者-消費者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use threads;
use Thread::Queue;
# 線程安全隊列:
my $q = Thread::Queue->new();
# 生產者:
threads->create(sub {
$q->enqueue($_) for 1..5;
$q->end(); # 標記結束
});
# 消費者:
my $worker = threads->create(sub {
while (defined(my $item = $q->dequeue())) {
say "處理: $item";
}
});
$worker->join();
# dequeue 在隊列為空時阻塞
# enqueue 支持批量:
# $q->enqueue(@items);
# 隊列非常適合生產者-消費者模型

線程參數

create 的第二參數起是傳給子程序的參數;詞法變量每線程獨立副本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use threads;
# 線程複製參數與詞法變量:
my $base = 10;
my $t = threads->create(sub {
my $num = shift;
return $num + $base; # $base 是副本
}, 5);
say $t->join(); # 15
# 每次 create 傳遞參數:
my @results = map {
threads->create(sub {
my $x = shift;
return $x * $x;
}, $_)->join();
} 1..3;
# 詞法變量每個線程一份副本
# 需要共享必須用 :shared

並行任務

固定線程數抓取任務處理;共享索引原子分配,避免每任務建線程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use threads;
use threads::shared;
# 簡單並行任務池:
my @jobs = 1..10;
my @workers;
my $total :shared = 0;
my $next :shared = 0;
# 固定 4 個工作線程:
for (1..4) {
push @workers, threads->create(sub {
while (1) {
my $job;
lock $next;
last if $next >= @jobs;
$job = $jobs[$next++];
# 做耗時工作:
$total += $job;
}
});
}
$_->join() for @workers;
say "總和: $total";
# 任務分塊交給固定線程池
# 避免每任務新建線程的開銷

線程池模式

隊列分發 + 結果隊列收集;end 多次調用喚醒多個阻塞線程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
use strict;
use warnings;
use threads;
use Thread::Queue;
# 固定線程池模式:
my $q = Thread::Queue->new();
my $result_q = Thread::Queue->new();
# 消費者線程池:
my @workers = map {
threads->create(sub {
while (defined(my $job = $q->dequeue())) {
my $out = process($job);
$result_q->enqueue($out);
}
})
} 1..4;
# 分發任務:
$q->enqueue($_) for 1..100;
$q->end() for 1..4; # 喚醒所有線程結束
# 收集結果:
my @results;
while (my $r = $result_q->dequeue()) {
push @results, $r;
}
$_->join() for @workers;
# 線程池複用線程,開銷最小
# 結果隊列避免直接寫共享結構

15.網絡編程

TCP 套接字、HTTP 客户端、URL 解析與 DNS 查詢。

TCP 客户端

IO::Socket::INET 創建連接;print 發送、<...> 讀取響應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
use IO::Socket::INET;
# TCP 客户端:
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 80,
Proto => 'tcp',
Timeout => 10,
) or die "無法連接: $@";
# 發送 HTTP 請求:
print $sock "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
# 讀取響應:
while (my $line = <$sock>) {
print $line;
}
close $sock;
# 創建失敗時 $@ 保存原因
# Timeout 防止無限阻塞

TCP 服務端

Listen 監聽端口,accept 接受連接;每個連接可回顯或交子進程處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use IO::Socket::INET;
# TCP 服務端:
my $server = IO::Socket::INET->new(
LocalAddr => '0.0.0.0',
LocalPort => 8080,
Proto => 'tcp',
Listen => 10,
ReuseAddr => 1,
) or die "無法監聽: $@";
say "監聽 8080 端口";
while (my $client = $server->accept()) {
# 每個連接簡單回顯:
my $line = <$client>;
print $client "收到: $line";
close $client;
}
# 多連接可結合 fork 處理
# ReuseAddr 避免重啓時端口占用

HTTP GET

HTTP::Tiny 是核心模塊;get 返回 status/content,可配合 JSON::PP 解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use HTTP::Tiny;
my $ua = HTTP::Tiny->new(
timeout => 10,
agent => 'Mozilla/5.0',
);
# GET 請求:
my $res = $ua->get('https://api.example.com/data');
if ($res->{success}) {
say "狀態: $res->{status}";
print $res->{content};
} else {
warn "請求失敗: $res->{status}";
}
# 解析 JSON 響應:
use JSON::PP;
my $data = decode_json($res->{content});
say $data->{name};
# HTTP::Tiny 是核心模塊
# 需要重定向/代理可配置選項

HTTP POST

post 發送任意 body;post_form 自動做 URL 編碼提交表單。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $ua = HTTP::Tiny->new();
# POST JSON:
my $payload = encode_json({
name => 'Rex',
age => 5,
});
my $res = $ua->post(
'https://api.example.com/users',
{
content => $payload,
headers => {
'Content-Type' => 'application/json',
},
},
);
# 表單編碼 POST:
my $form = $ua->post_form(
'https://example.com/login',
{ user => 'admin', pass => 'secret' },
);
# 檢查結果:
if ($res->{success}) {
say "已提交";
}
# post_form 自動做 URL 編碼
# 上傳文件用 multipart 或文件句柄

URL 解析

URI 模塊拆解與構建 URL;query_form 讀寫查詢參數,自動編碼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
use URI;
# 解析 URL:
my $uri = URI->new('https://user:[email protected]:8080/path?a=1&b=2#frag');
say $uri->scheme; # https
say $uri->host; # example.com
say $uri->port; # 8080
say $uri->path; # /path
# 查詢參數:
my %query = $uri->query_form;
say $query{a}; # 1
# 構建 URL:
my $u = URI->new('https://example.com/api');
$u->query_form(name => 'Rex', page => 2);
print $u; # 帶編碼的查詢串
# 拼接相對路徑:
my $abs = URI->new_abs('about.html', 'https://site.com/docs/');
# URI 模塊處理編碼轉義
# 別手拼 URL,用 URI 保證正確

DNS 查詢

Net::DNS 解析 A/MX/TXT 記錄;search 返回響應對象,需外部模塊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Net::DNS;
# 查詢 A 記錄:
my $resolver = Net::DNS::Resolver->new();
my $query = $resolver->search('example.com');
if ($query) {
for my $rr ($query->answer) {
if ($rr->type eq 'A') {
say $rr->address;
}
}
} else {
warn "查詢失敗: ", $resolver->errorstring;
}
# 其他記錄類型:
# $resolver->search('example.com', 'MX');
# $resolver->search('example.com', 'TXT');
# 或手動指定服務器:
Net::DNS::Resolver->new(nameservers => ['8.8.8.8']);
# 需要 Net::DNS 模塊(非核心)

套接字選項

setsockopt 設置底層選項;timeout 控制阻塞;IO::Select 非阻塞等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use IO::Socket::INET;
my $sock = IO::Socket::INET->new(
PeerHost => 'example.com',
PeerPort => 443,
Proto => 'tcp',
Timeout => 5,
) or die "連接失敗";
# 設置選項:
$sock->setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1);
# 超時:
$sock->timeout(10);
# 讀寫:
print $sock "ping\n";
my $reply = <$sock>;
# 非阻塞讀:
use IO::Select;
my $sel = IO::Select->new($sock);
if ($sel->can_read(2)) {
my $data = <$sock>;
}
# SO_RCVBUF 等緩衝選項可調
# IO::Select 實現多句柄等待

REST 客户端

封裝 HTTP::Tiny 統一處理鑑權與 JSON;錯誤統一拋給調用方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use HTTP::Tiny;
use JSON::PP;
my $base = 'https://api.example.com/v1';
my $ua = HTTP::Tiny->new(timeout => 10);
# 通用 REST 封裝:
sub api {
my ($method, $path, $body) = @_;
my $url = $base . $path;
my %opts = (headers => {
'Accept' => 'application/json',
'Authorization' => 'Bearer token123',
});
$opts{content} = encode_json($body) if $body;
my $res = $ua->request($method, $url, \%opts);
return decode_json($res->{content})
if $res->{success};
die "API 錯誤: $res->{status}";
}
my $user = api('GET', '/users/1');
my $created = api('POST', '/users',
{ name => 'Rex', age => 5 });
# 錯誤統一拋給調用方
# 分頁/重試按需增強

16.日期與時間

time/localtime、strftime 格式化、Time::Piece 與 DateTime 處理。

localtime 分解

time 取 epoch;localtime 拆成年月日時分秒,注意月與年的偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use strict;
use warnings;
use feature 'say';
# localtime 返回時間各部分:
my ($sec, $min, $hour, $mday, $mon, $year,
$wday, $yday, $isdst) = localtime;
# 月份從 0 開始,年份是偏移:
my $display = sprintf "%04d-%02d-%02d %02d:%02d:%02d",
$year + 1900, $mon + 1, $mday,
$hour, $min, $sec;
say $display;
# gmtime 返回 UTC:
my @utc = gmtime;
# time 返回當前 epoch:
my $now = time;
say "當前時間戳: $now";
# localtime 依賴系統時區
# 格式化推薦 strftime 更清晰

strftime 格式化

POSIX::strftime 用 %Y %m %d 等佔位符格式化;%A %B 輸出星期與月份名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use strict;
use warnings;
use POSIX 'strftime';
# strftime 格式化時間:
my $now = time;
my $fmt = strftime(
'%Y-%m-%d %H:%M:%S', localtime $now);
say $fmt; # 2026-08-02 12:34:56
# 常用格式:
# %Y 四位數年份 %y 兩位
# %m 月 %d 日 %H 時 %M 分 %S 秒
# %A 星期全名 %a 縮寫
# %B 月份全名 %b 縮寫
# %j 一年中的第幾天
my $date = strftime('%A %B %d', localtime);
my $custom = strftime('%Y/%m/%d %I:%M %p',
localtime); # 12 小時制
# strftime 是 C 函數,本地化輸出

時間戳轉換

timelocal/timegm 把本地或 UTC 時間數組轉回 epoch,注意偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use Time::Local;
# time 返回當前 epoch 秒:
my $now = time;
# epoch 轉數組(本地/UTC):
my @local = localtime $now;
my @utc = gmtime $now;
# 數組轉 epoch:
use Time::Local 'timelocal';
my $epoch = timelocal(
$sec, $min, $hour, $mday, $mon, $year);
# 從日期字符串解析 epoch:
my $ts = timelocal(0, 0, 12, 2, 7, 126);
say "2026-08-02 12:00 的 epoch: $ts";
# timegm 處理 UTC:
use Time::Local 'timegm';
my $utc_epoch = timegm(0, 0, 0, 1, 0, 126);
# 注意月份與年份的偏移(月 -1,年 -1900)

sleep 延時

sleep 整數秒;Time::HiRes 支持小數與微秒;select 可做無阻塞等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# sleep 阻塞指定秒數:
sleep 2;
say "睡醒了";
# 支持小數(需要 Time::HiRes):
use Time::HiRes qw(sleep usleep);
sleep 0.5; # 半秒
usleep 500000; # 微秒
# 定時循環:
for (1..3) {
say "第 $_ 次";
sleep 1;
}
# 非阻塞延時方案:
# 用 select 空超時:
use IO::Select;
my $sel = IO::Select->new();
$sel->can_read(0.5); # 等待但不讀
# sleep 在信號中斷後返回剩餘秒數
# 生產調度用 Time::HiRes 更精確

耗時測量

time 秒級計時;Time::HiRes 毫秒級;跨日/時區差異用 DateTime。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# 簡單計時(秒):
my $t0 = time;
do_work();
my $elapsed = time - $t0;
say "耗時 $elapsed 秒";
# 高精度計時:
use Time::HiRes qw(time);
my $s = time;
do_work();
my $ms = (time - $s) * 1000;
say "耗時 $ms 毫秒";
# 跨時區/日期差異用 DateTime:
use DateTime;
my $a = DateTime->now;
my $b = $a->clone->add(days => 3);
my $dur = $b - $a;
say $dur->in_units('hours'); # 72
# DateTime 返回持續時間對象
# 處理夏令時優先用 DateTime

DateTime 對象

DateTime 完整時間處理:構造、取值、時區轉換與日期算術。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use DateTime;
# 構造與取值:
my $dt = DateTime->now;
my $d2 = DateTime->new(
year => 2026, month => 8, day => 2,
hour => 12, minute => 30,
);
say $d2->ymd; # 2026-08-02
say $d2->hms; # 12:30:00
say $d2->year;
# 時區:
my $tokyo = DateTime->now(time_zone => 'Asia/Tokyo');
my $utc = $tokyo->set_time_zone('UTC');
# 算術:
my $later = $dt->clone->add(days => 7, hours => 3);
my $earlier = $dt->clone->subtract(months => 1);
# 格式化:
say $dt->format_cldr('yyyy-MM-dd HH:mm');
# DateTime 是外部模塊,功能全面
# 處理夏令時/時區轉換很安全

字符串解析

Time::Piece::strptime 按格式解析日期字符串,得到 epoch 與差值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use Time::Piece;
# Time::Piece 自帶 strptime 解析:
my $t = Time::Piece->strptime(
'2026-08-02 12:30:00', '%Y-%m-%d %H:%M:%S');
say $t->epoch;
# 解析其他格式:
my $d = Time::Piece->strptime(
'02/Aug/2026', '%d/%b/%Y');
# 格式化輸出:
say $t->strftime('%Y/%m/%d');
# epoch 構造:
my $now = localtime; # Time::Piece 對象
say $now->epoch;
say $now->year; # 2026
# 差值:
my $diff = $t - $now;
say $diff->days;
# Time::Piece 是核心模塊
# strptime 失敗會拋異常

格式對象

DateTime::Format::Strptime 統一解析與格式化;RFC3339 處理 ISO 時間。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use DateTime;
# DateTime 格式化與解析:
use DateTime::Format::Strptime;
my $format = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %H:%M:%S',
time_zone => 'UTC',
);
# 解析字符串:
my $dt = $format->parse_datetime(
'2026-08-02 12:00:00');
# 格式化輸出:
my $str = $format->format_datetime($dt);
say $str;
# 常用 pattern:
# %Y 年 %m 月 %d 日
# %H 時 %M 分 %S 秒 %z 時區偏移
my $iso = $dt->format_cldr('yyyy-MM-dd HH:mm:ss');
# 更簡潔的 RFC3339:
use DateTime::Format::RFC3339;
my $rfc = DateTime::Format::RFC3339->parse_datetime(
'2026-08-02T12:00:00Z');
# 用格式對象統一解析/格式化

17.進程與命令

system/反引號執行命令、環境變量、@ARGV、信號與管道。

執行命令

反引號捕獲輸出;system 執行不捕獲;列表形式避免 shell 注入。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
use strict;
use warnings;
use feature 'say';
# 反引號捕獲輸出:
my $out = `ls -la`;
print $out;
# qx 更清晰(可定製分隔符):
my $date = qx(date);
chomp $date;
# system 執行但不捕獲:
system("echo hello");
# system 返回退出狀態
# 反引號在標量上下文返回整段輸出
# 列表上下文按行拆分:
my @lines = `cat file.txt`;
# 注意轉義與注入:
# 使用列表形式避免 shell 拼接:
system("ls", "-l");
# 別把用户輸入拼進命令字符串

exec 與 system

exec 替換當前進程;system 等待子進程結束;$? 保存退出狀態。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use strict;
use warnings;
# exec 替換當前進程:
# exec "ls", "-l"; # 之後代碼不再執行
# system 等待子進程結束:
system("echo", "hi");
say "system 之後";
# 返回值是退出碼:
my $status = system("true");
say "退出碼: $status"; # 0
# $? 保存最後一次狀態:
my $exit = $? >> 8; # 真實退出碼
my $sig = $? & 127; # 信號
# exec 與 system 都不捕獲輸出:
# 需要輸出用反引號或 open 管道
# exec 適合替換為另一個程序

環境變量

%ENV 哈希讀寫環境變量,影響後續子進程;local 可臨時隔離。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 讀取環境變量:
my $home = $ENV{HOME};
say "主目錄: $home";
# 設置環境變量:
$ENV{PATH} = '/usr/bin:/bin';
$ENV{LANG} = 'en_US.UTF-8';
# 遍歷所有環境變量:
for my $key (sort keys %ENV) {
print "$key=$ENV{$key}\n";
}
# 傳給子進程:
$ENV{MODE} = 'test';
system("echo", $ENV{MODE});
# 刪除環境變量:
delete $ENV{SECRET};
# %ENV 影響後續所有子進程
# 腳本環境隔離用 local:
{
local $ENV{DEBUG} = 1;
run_tests();
} # 塊結束恢復

命令行參數

@ARGV 是參數列表;複雜參數解析用 Getopt::Long 聲明式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# @ARGV 是命令行參數:
my ($file, $count) = @ARGV;
say "文件: $file, 次數: $count";
# 參數即數組:
my @files = @ARGV;
for my $f (@files) {
say "處理: $f";
}
# shift 默認從 @ARGV 取:
my $first = shift @ARGV;
# 沒有參數時默認讀 STDIN:
# while (<>) { ... }
# 參數解析建議用 Getopt::Long:
use Getopt::Long;
my ($verbose, $name);
GetOptions(
'verbose' => \$verbose,
'name=s' => \$name,
);
say $verbose ? "詳細模式" : "簡潔模式";
say "名字: $name" if $name;
# $0 是腳本名

退出狀態

exit N 設置退出碼;0 成功非 0 失敗;END 塊在退出前執行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
# exit 退出並設置狀態碼:
exit 0; # 成功
# exit 1; # 失敗
# 不加參數退出碼為 0
# 條件退出:
my $ok = check();
exit 1 unless $ok;
# die 的退出碼是 255(或 $!):
# 失敗場景也可:
if ($err) {
print STDERR "失敗: $err\n";
exit 2;
}
# $? 與 system:
system("false");
say $?; # 256(128 + 退出碼)
my $code = $? >> 8; # 1
# 與 shell 交互的約定:
# 0 成功;非 0 失敗
# END 塊在退出前執行:
END { print "清理\n" }
# 信號中斷時用 $? 判斷原因

信號處理

$SIG{INT} 捕獲信號;處理函數只設標誌,主循環再響應。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# 捕獲信號:
$SIG{INT} = sub {
print "收到中斷,清理退出\n";
exit 1;
};
$SIG{TERM} = 'DEFAULT'; # 恢復默認
$SIG{QUIT} = 'IGNORE'; # 忽略
# 等待循環:
while (1) { sleep 1 }
# 發送信號:
kill 'TERM', $pid;
# 給子進程發信號:
my $pid = fork();
if ($pid == 0) {
exit 0; # 子進程
}
kill 'USR1', $pid;
# 信號處理裏別做重活:
# 只設標誌,主循環處理:
my $stop = 0;
$SIG{INT} = sub { $stop = 1 };
while (!$stop) { work() }
# %SIG 是全局哈希
# Windows 上信號支持有限

進程管道

-| 讀子進程輸出、|- 寫入子進程輸入;雙向交互用 IPC::Open3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 管道讀取命令輸出:
open my $fh, '-|', 'grep error /var/log/app.log'
or die "無法執行: $!";
while (my $line = <$fh>) {
chomp $line;
say "匹配: $line";
}
close $fh;
# 管道寫入:
open my $out, '|-', 'gzip > out.gz'
or die "無法執行: $!";
print $out "要壓縮的數據\n";
close $out;
# 替代寫法 open2/open3:
use IPC::Open3;
my $pid = open3(
my $in, my $err, 'cmd', 'args');
# '-|' 讀子進程 stdout
# '|-' 寫子進程 stdin
# 雙向交互用 IPC::Open3

捕獲輸出

重定向合併 stdout/stderr;$? 檢查退出碼;Capture::Tiny 結構化捕獲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 捕獲 stdout + stderr:
my $out = `cmd 2>&1`;
# 只捕獲 stderr:
my $err = `cmd 2>&1 1>/dev/null`;
# 檢查退出碼(反引號):
my $result = `grep pattern file.txt`;
my $exit = $? >> 8;
if ($exit == 0) {
say "找到匹配";
} else {
say "無匹配或出錯";
}
# 捕獲時防止超時:
# 複雜場景用 IPC::Open2 或 IPC::Run
# 安全執行並取輸出:
use Capture::Tiny qw(capture);
my ($stdout, $stderr, $exit) =
capture { system("ls", "-l") };
say $stdout;
# Capture::Tiny 是外部模塊
# 大量命令交互考慮用 IPC::Run

18.正則表達式

模式匹配、字符類、量詞、捕獲組、預編譯與替換。

匹配基礎

=~ 匹配、!~ 非匹配;錨點 ^ $ \b 定位;i 修飾符忽略大小寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 匹配操作符 =~ m//:
my $str = "hello world";
if ($str =~ /world/) {
say "包含 world";
}
# 非匹配 !~:
if ($str !~ /xyz/) {
say "不含 xyz";
}
# 錨點:
/^start/; # 行首
/end$/; # 行尾
/\bword\b/; # 詞邊界
# 忽略大小寫:
/hello/i;
# 匹配一次並取結果:
my ($match) = $str =~ /(\w+) (\w+)/;
# 更多修飾符見下文各節

匹配與提取

g 全局匹配;列表上下文返回所有命中;結合 grep 過濾數組。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 判斷是否匹配:
if ($line =~ /error/) { ... }
# 列表上下文取全部命中:
my $str = "x1y2z3";
my @nums = $str =~ /(\d)/g; # 1 2 3
# 統計匹配次數:
my $count = () = $str =~ /\d/g;
# 組合 grep 過濾:
my @words = qw(cat dog bird);
my @short = grep { length $_ < 4 } @words;
# 變量插值構造模式:
my $pat = "a.c";
$str =~ /$pat/; # . 是任意字符
# 預編譯正則見後續主題:
my $re = qr/\d{3}-/;
$str =~ $re;
# grep/map 用 $_ 隱式迭代

字符類

\d \w \s 及其取反;自定義 [...] 與排除 [^...];量詞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
# 常用字符類:
# \d 數字 [0-9]
# \w 單詞字符 [A-Za-z0-9_]
# \s 空白 [ \t\n\r\f]
# 大寫取反:\D \W \S
# 自定義字符類:
/[aeiou]/; # 元音
/[0-9a-fA-F]/; # 十六進制
/[^0-9]/; # 非數字
# 量詞:
/a*/; # 0 或多個
/a+/; # 1 或多個
/a?/; # 0 或 1 個
/a{3}/; # 恰好 3 個
/a{2,4}/; # 2 到 4 個
/a{2,}/; # 至少 2 個
# 分組與擇一:
/(foo|bar)/;
/(ab)+/;
# 轉義特殊字符:
/\$dollar/;
/1\+1/;
# 點號 . 默認匹配任意字符(除換行)

量詞與回溯

貪婪默認、非貪婪加 ?;佔位量詞 + 禁止回溯;嵌套量詞警惕災難回溯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use strict;
use warnings;
# 貪婪 vs 非貪婪:
my $s = "<b>hi</b><b>yo</b>";
my @greedy = $s =~ /<b>.*<\/b>/g; # 整個串
my @lazy = $s =~ /<b>.*?<\/b>/g; # 兩個小塊
# 量詞:
# * 貪婪 0+ *? 非貪婪
# + 貪婪 1+ +? 非貪婪
# ? 0/1 ?? 非貪婪
# {n,m} {n,m}? 非貪婪
# 佔有量詞(禁止回溯):
my $big = "aaaaa";
$big =~ /a++a/; # 失敗(a++ 佔滿)
$big =~ /a+a/; # 成功(回溯)
# 行錨點修飾:
/^foo/m; # m 讓 ^$ 匹配每行
/foo./s; # s 讓 . 匹配換行
# 性能注意:嵌套量詞易災難回溯
# 簡單目標用固定字符類替代

捕獲組

$1 $2 按左括號順序取捕獲;(?:) 非捕獲;(?<name>) 具名捕獲。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
use strict;
use warnings;
use feature 'say';
# 捕獲組 $1 $2:
my $s = "name=alice age=30";
if ($s =~ /name=(\w+) age=(\d+)/) {
say "名字: $1, 年齡: $2";
}
# 列表上下文取所有捕獲:
my ($name, $age) = $s =~ /name=(\w+) age=(\d+)/;
# 嵌套捕獲:
if ("abc123" =~ /((\w+)(\d+))/) {
say $1; # abc123
say $2; # abc
say $3; # 123
}
# 非捕獲組:
"hello" =~ /(?:he)(llo)/; # $1 是 llo
# 具名捕獲:
if ("Rex 5" =~ /(?<who>\w+) (?<num>\d+)/) {
say $+{who}; # Rex
say $+{num}; # 5
}
# $1-$9 從 1 開始,未匹配為 undef
# 捕獲組順序按左括號出現順序

具名捕獲

(?<name>...) 具名組 + %+ 哈希訪問;\k<name> 反向引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 具名捕獲組:
my $line = "2026-08-02 12:30";
if ($line =~ /(?<year>\d{4})-(?<mon>\d{2})-(?<day>\d{2}) (?<time>\d{2}:\d{2})/) {
say "年: $+{year}";
say "月: $+{mon}";
say "日: $+{day}";
say "時間: $+{time}";
}
# %+ 哈希訪問:
say $+{year};
# 反向引用:
/(?<pair>\w)\k<pair>/; # 重複字符
# 數字反向引用:
/(\w)\1/;
# 分支重置:
"ab" =~ /(?|a(b)|c(d))/; # $1 無論哪個分支
# 具名捕獲讓代碼可讀性大增
# %- 保存所有同名捕獲

預編譯正則

qr// 編譯一次複用;模塊內共享提升大循環性能;可帶修飾符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use strict;
use warnings;
use feature 'say';
# 預編譯正則:
my $email_re = qr/[\w.]+@[\w.]+\.\w+/;
my $phone_re = qr/1[3-9]\d{9}/;
# 複用匹配:
my $text = "聯繫 a@b.com 或 13800138000";
my @emails = $text =~ /($email_re)/g;
my @phones = $text =~ /($phone_re)/g;
say "郵箱: @emails";
# 插值到更大正則:
my $full = qr/($email_re|$phone_re)/;
# 模塊內共享(編譯一次):
{
my $DATE_RE = qr/\d{4}-\d{2}-\d{2}/;
sub is_date { $_[0] =~ $DATE_RE }
}
# 預編譯避免每次匹配重新編譯
# 大循環裏性能提升明顯
# qr 可帶修飾符:
my $ci = qr/foo/i;

替換與翻譯

s/// 替換、g 全局、i 忽略大小寫;替換部分可用 $1;tr/// 字符翻譯。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# s/// 替換:
my $s = "apple apple";
$s =~ s/apple/orange/; # 只換第一個
say $s; # orange apple
# g 全局替換:
$s =~ s/apple/kiwi/g;
say $s; # kiwi kiwi
# i 忽略大小寫:
"HELLO" =~ s/hello/hi/i; # hi
# 替換中使用捕獲:
my $date = "2026-08-02";
$date =~ s/(\d{4})-(\d{2})-(\d{2})/$2\/$3\/$1/;
# 在替換部分用 $1 $2
# 空替換可計數:
my $count = ($s =~ s/x//g);
# tr/// 字符級翻譯(無正則):
my $t = "hello";
$t =~ tr/a-z/A-Z/; # HELLO
# s 返回替換次數,標量上下文
# 轉義替換部分的反斜槓要小心

19.模塊與工程化

模塊結構、use/require、選項解析、CPAN 安裝與測試。

模塊結構

包名對應文件路徑;Exporter 導出;1; 結尾讓 use 成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package My::Greet;
use strict;
use warnings;
# 導出列表:
use Exporter 'import';
our @EXPORT_OK = qw(greet);
our @EXPORT = qw(); # 默認不導出
sub greet {
my $name = shift;
return "hello, $name";
}
# 版本號:
our $VERSION = '0.01';
1; # 模塊必須返回真
# 文件名對應包名:
# My/Greet.pm → My::Greet
# @EXPORT 自動導出;@EXPORT_OK 需顯式請求

use 與 require

use 編譯期加載並導入;require 運行時加載;do 執行文件不建符號表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use feature 'say';
# use 編譯期加載 + 導入:
use List::Util 'sum';
say sum(1, 2, 3);
# 只加載不導入:
use List::Util ();
# require 運行時加載:
require JSON::PP;
say JSON::PP::encode_json({a => 1});
# do 執行文件(無符號表):
my $cfg = do './config.pl';
# 檢查是否已加載:
my $loaded = $INC{'List/Util.pm'};
# 加載失敗:
# use 在編譯期失敗會終止
# require 失敗拋異常,可捕獲:
eval { require Missing::Module };
say "加載失敗: $@" if $@;

模塊安裝

cpanm 安裝 CPAN 模塊;核心模塊隨 Perl 發佈;cpanfile 記錄依賴。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
use strict;
use warnings;
use feature 'say';
# 查找已裝模塊:
# perldoc -l Module
# 查看模塊信息:
# perldoc Module::Name
# cpanm 安裝:
# cpanm DateTime
# 指定版本:
# 腳本里檢查可用性:
use List::Util; # 核心模塊隨 Perl 發佈
# 核心 vs CPAN 區分:
# 核心:隨 Perl 一起發佈,無需安裝
# CPAN:需自行安裝(如 Moose)
# 建議依賴核心模塊保持可移植:
use JSON::PP; # 核心
use HTTP::Tiny; # 核心
use Time::Piece; # 核心
# 查看核心模塊列表:
# corelist -a Time::Piece
# 項目依賴用 cpanfile 記錄

Getopt::Long

聲明式選項解析:=s 字符串 =i 整數、開關、別名與取反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use strict;
use warnings;
use Getopt::Long;
# 聲明選項:
my ($verbose, $name, $count, $help);
GetOptions(
'verbose|v' => \$verbose,
'name=s' => \$name,
'count=i' => \$count,
'help|h' => \$help,
) or die "參數錯誤";
# 使用:
if ($help) { print_usage(); exit 0 }
say $verbose ? "詳細模式" : "簡潔模式";
say "名字: $name" if defined $name;
say "次數: $count" if defined $count;
# 常用類型:
# =s 字符串 =i 整數 =f 浮點
# =s@ 多個值 ! 取反開關
# 長選項支持 --name=value 與 --name value
# 短選項合併:-vh 等價 -v -h
# GetOptions 支持縮寫(無歧義時)

常用 CPAN 模塊

DateTime/Moose/DBI 等生態;MetaCPAN 檢索;eval 探測可用性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
use strict;
use warnings;
use feature 'say';
# 常用 CPAN 模塊(需安裝):
# DateTime 日期時間處理
# Moose / Moo 對象系統
# DBI 數據庫訪問
# Mojo::UserAgent Web 客户端
# JSON::XS 快速 JSON
# LWP::UserAgent 老牌 HTTP 客户端
# Text::CSV CSV 解析
# XML::LibXML XML 解析
# 安裝方式:
# cpanm Mojo::UserAgent
# 或在腳本中探測:
my $has_json = eval { require JSON::XS; 1 };
# 使用樣例(Mojolicious):
# use Mojo::UserAgent;
# my $ua = Mojo::UserAgent->new;
# my $tx = $ua->get('https://example.com');
# say $tx->result->body;
# 查看已安裝模塊:
# perldoc perlmodlib
# 檢索模塊用 MetaCPAN:
# https://metacpan.org

腳本與命令行

shebang 指定解釋器;perl -w/-e/-ne 常用命令行選項。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
# 可執行腳本:
# chmod +x script.pl
# ./script.pl 直接運行
# 便攜 shebang 二選一:
# #!/usr/bin/env perl
# 會用 PATH 中的 perl(虛擬環境友好)
# 命令行運行:
# perl script.pl
# perl -w script.pl # 開警告
# perl -Mstrict script.pl # 開 strict
# perl -e 'print "hi"' # 單行執行
# perl -ne 'print if /foo/' file # 逐行處理
# 參數檢查:
my ($input, $output) = @ARGV;
die "用法: $0 <in> <out>\n" unless $input && $output;
say "處理 $input$output";
# $0 腳本名;@ARGV 參數列表
# 腳本頂部務必 use strict + warnings

打包與目錄

lib/t 目錄佈局、Makefile.PL 構建;-Ilib 本地開發加載。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package My::Lib;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = qw(helper);
our $VERSION = '0.01';
sub helper { return 42 }
1;
# 目錄佈局:
# lib/My/Lib.pm 模塊本體
# t/01-basic.t 測試
# Makefile.PL / cpanfile 構建説明
# Changes / README 變更與文檔
# 本地開發加載:
# 用 -I 指定庫目錄:
# perl -Ilib t/01-basic.t
# 或 PERL5LIB=lib
# 構建與測試(經典流程):
# perl Makefile.PL
# make
# make test
# 模塊打包發佈:
# cpanm 可從本地目錄安裝
# 版本號變更記得同步 Changes

Test::More

plan 聲明斷言數;is/ok/like 斷言;prove -l 批量跑測試。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use strict;
use warnings;
use Test::More;
# 聲明測試數量:
plan tests => 4;
# 斷言:
is(add(2, 3), 5, "2+3=5");
ok(1 == 1, "真值斷言");
isnt("a", "b", "不等");
like("hello", qr/^he/, "匹配斷言");
sub add { $_[0] + $_[1] }
# 測試文件獨立運行:
# perl t/01-basic.t
# 或配合 prove:
# prove -l t/
# 更多斷言:
# is_deeply 比較複雜結構
# cmp_ok 自定義比較
# done_testing 自動統計數量
# 跳過條件測試:
# SKIP: { skip "Windows 不支持", 1 }
# Test::More 是核心模塊

官方鏈接

直達官方文檔與資源。

關於本速查

本頁是 Perl 5.38 的自包含速查手冊,覆蓋語言核心與最常用內置函數(含少量 CPAN 模塊)在真實項目中約 80% 的常見用法。內容偏向現代慣用法:`use strict` + `use warnings` 必備、後綴式控制流、scalar/list 上下文區分、引用構建複雜數據結構、`say` / `state` / `signatures`。Perl 是 Larry Wall 於 1987 年發佈的多範式動態語言,以「不止一種方法做同一件事(TMTOWTDI)」著稱,在文本處理、系統管理與 Web 後端領域有深厚積累。權威參考見隨 Perl 安裝的 `perldoc` 與 [Perl 官方文檔](https://perldoc.perl.org/)。 19 個章節各自聚焦一個主題——從第一個程序到引用、bless 面向對象與常見誤區。每節拆成 8–14 個帶示例的小節(每個 5–20 行),共約 150 個主題。代碼片段刻意短小、自解釋,複製後可直接用 `perl` 運行。 所有處理都在瀏覽器中完成——無上傳、無追蹤。本頁是 GuruToolkit 免費開發者工具集的一部分;代碼片段可自由使用,無任何擔保。

版本 2.1.0