本工具使用的开源库

本工具代码中捆绑了 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