Perl 教程
什么是 Perl?
Perl 由 Larry Wall 于 1987 年发布,是一种功能强大的动态脚本语言,以文本处理能力闻名,被称为"瑞士军刀"。它吸取了 C、sed、awk、shell 等语言的特点,在系统管理、文本处理、Web 开发(CGI 时代)领域曾占据统治地位。
Perl 的核心特点
- 文本处理王:正则表达式内建于语言,处理日志/文本效率极高
- 灵活多变:TMTOWTDI(There's More Than One Way To Do It,条条大路通罗马)
- 快速开发:脚本语言,无需编译,即写即跑
- CPAN 生态:全球最大的开源模块库之一(20 万+ 模块)
- 跨平台:Unix/Linux/Windows/macOS
Perl 能做什么
- 系统管理与自动化脚本
- 日志分析与文本处理
- Web 开发(CGI、Mojolicious 框架)
- 生物信息学(著名的 BioPerl)
- 网络编程、数据库操作
Hello World
#!/usr/bin/perl
use strict;
use warnings;
print "Hello, World!\n";
# 运行
perl hello.pl
# 或直接执行(需可执行权限)
chmod +x hello.pl
./hello.pl
Perl 简介
Perl 简介
Perl(Practical Extraction and Report Language,实用提取与报表语言)的定位就是处理文本与生成报表。
发展历程
1987 Perl 1(Larry Wall 发布)
1989 Perl 3
1991 Perl 4
1994 Perl 5(至今主流)
2000+ 大量 Web 应用(CGI 时代巅峰)
2015 Perl 6 改名 Raku(另立门户)
2023 Perl 5.38(仍在持续更新)
为什么曾如此流行
1. 文本处理能力无与伦比(正则内建)
2. 开发效率高(脚本语言)
3. 系统管理神器(Unix 世界标配)
4. 免费开源、跨平台
5. 庞大的 CPAN 模块库
Perl 的哲学
TMTOWTDI: 实现同一功能有多种方式
"易事容易,难事可能" (Easy things easy, hard things possible)
上下文敏感: 同一操作在不同上下文含义不同
my $num = 5; # 标量上下文
my @list = (1, 2, 3); # 列表上下文
现状与定位
新项目已较少用 Perl(被 Python/Go 取代)
但仍大量存在于:
- 遗留系统维护
- Unix 系统脚本
- 生物信息等领域
- 文本处理的快速脚本
vs Python
Perl: 正则/文本更强,语法自由
Python: 可读性更好,生态更现代
选择: 新项目推荐 Python;维护老系统需要 Perl
Perl 环境安装
Perl 环境安装
Perl 通常已预装在 Unix/Linux/macOS 上,Windows 需要额外安装。
1. Linux(通常自带)
# 检查
perl -v
# Ubuntu/Debian(如需安装)
sudo apt update
sudo apt install perl
# 查看位置
which perl # /usr/bin/perl
2. macOS(自带)
# 自带 perl,检查版本
perl -v
# 需要新版可用 brew
brew install perl
3. Windows
# 方案一: Strawberry Perl(推荐,含编译器)
# https://strawberryperl.com/
下载安装,自动加入 PATH
# 方案二: ActivePerl
# https://www.activestate.com/products/perl/
# 验证
perl -v
4. 安装 CPAN 模块
# 命令行安装
cpan install JSON::PP
# 或交互式
cpan
cpan> install LWP::Simple
cpan> quit
# 或 cpanm(更友好)
cpan App::cpanminus
cpanm JSON
cpanm DBI
# 国内镜像(加快下载)
# PERL_CPANM_OPT 环境变量或配置镜像
5. 第一个脚本
#!/usr/bin/perl
use strict;
use warnings;
my $name = "World";
print "Hello, $name!\n";
# 运行
perl hello.pl
6. 常用开发工具
编辑器: VS Code(Perl 插件)、Vim、Emacs
IDE: Padre(Perl IDE)
调试: perl -d script.pl(内置调试器)
检查: perl -c script.pl(语法检查)
格式化: perltidy
Perl 基础语法
Perl 基础语法
Perl 语法融合了 C 与 shell 的特点,初学者需要适应其灵活性。
1. 变量(三种基本类型)
# 标量: 单个值
my $name = "张三";
my $age = 28;
# 数组: 有序列表
my @fruits = ("苹果", "香蕉", "橙子");
# 哈希: 键值对
my %person = (name => "张三", age => 28);
2. 变量类型符号
$scalar # 标量(单个值)
@array # 数组
%hash # 哈希
&sub # 子程序(函数)
*glob # 类型团
# 访问数组元素
$fruits[0] # "苹果"($ 开头!)
# 访问哈希
$person{name} # "张三"({} 而非 [])
3. 语句与分号
# 语句以分号结尾
my $x = 1;
my $y = 2;
# 注释用 #
# 这是注释
my $z = 3; # 行尾注释
4. 引号
my $name = "张三";
# 双引号: 支持插值
print "你好, $name\n"; # 变量展开
# 单引号: 原样输出
print '你好, $name\n'; # 字面量 $name
# q/qq 简化
my $s1 = q(单引号字符串);
my $s2 = qq(双引号 $name);
5. 打印输出
print "Hello\n"; # 简单打印
print "a", "b", "\n"; # 多个参数
printf "%s 今年 %d 岁\n", "张三", 28; # 格式化
say "自动换行"; # 需要 use feature 'say';
6. use strict 与 use warnings
#!/usr/bin/perl
use strict; # 强制变量声明(推荐)
use warnings; # 开启警告(推荐)
# 不声明会报错
my $x = 10; # 必须用 my 声明
# 未初始化警告
my $y;
print $y; # Use of uninitialized value
7. 特殊变量
$_ 默认变量(很多函数默认用它)
$0 脚本名
$$ 进程 ID
@ARGV 命令行参数
@_ 子程序参数
$! 系统错误信息
$? 子进程退出状态
Perl 数据类型
Perl 数据类型
Perl 有三种主要数据类型:标量、数组、哈希,还有引用与文件句柄。
1. 标量(Scalar)
# 标量可以是数字、字符串、引用
my $int = 42; # 整数
my $float = 3.14; # 浮点
my $str = "hello"; # 字符串
my $bool = 1; # Perl 无布尔类型,真/假
my $ref = \@array; # 引用
# 字符串与数字自动转换
my $n = "10" + 5; # 15(字符串变数字)
my $s = "num: " . 10; # 拼接(.)操作符
2. 数组(Array)
my @arr = (1, 2, 3, "a", "b");
# 访问
$arr[0] # 1
$arr[-1] # "b" 倒数
$#arr # 4 最后一个索引
# 长度
scalar @arr # 5
# 切片
@arr[1, 3] # (2, "a")
@arr[0..2] # (1, 2, 3)
# 常用操作
push @arr, "c"; # 末尾添加
pop @arr; # 取出末尾
shift @arr; # 取出开头
unshift @arr, 0; # 开头添加
splice @arr, 1, 2; # 删除中间
3. 哈希(Hash)
my %person = (
name => "张三",
age => 28,
city => "北京",
);
# 访问
$person{name} # "张三"
# 修改/添加
$person{age} = 29;
$person{email} = "z@example.com";
# 删除
delete $person{city};
# 判断存在
exists $person{name}; # 真
# 遍历
while (my ($k, $v) = each %person) {
print "$k = $v\n";
}
# 键/值
my @keys = keys %person;
my @vals = values %person;
4. 引用(Reference)
# 引用: 指向数据的指针
my @arr = (1, 2, 3);
my $arr_ref = \@arr;
# 解引用
@$arr_ref; # 整个数组
$arr_ref->[0]; # 1
# 匿名引用
my $hash_ref = { name => "张三" };
my $array_ref = [1, 2, 3];
# 访问
$hash_ref->{name}; # "张三"
$array_ref->[1]; # 2
# 复杂结构
my $data = {
users => [
{ name => "张三", age => 28 },
{ name => "李四", age => 30 },
],
};
print $data->{users}[0]{name}; # "张三"
5. 判断数据类型
ref($var) # 引用类型或空
defined($var) # 是否已定义
exists($h{key}) # 哈希键是否存在
Perl 变量
Perl 变量
掌握变量的声明、作用域与上下文是 Perl 的关键。
1. 变量声明(my / our / local)
# my: 词法作用域(推荐)
my $local = 10; # 仅在当前块/文件有效
# our: 包级全局
our $global = 20;
# local: 临时修改全局变量
local $/ = "\n"; # 临时修改特殊变量
{
my $inner = 5; # 块内可见
}
# $inner 在这里不可用
2. 作用域示例
my $x = "外层";
sub test {
my $x = "内层"; # 遮蔽外层
print $x; # 内层
}
test();
print $x; # 外层
3. 变量上下文
# 标量上下文 vs 列表上下文
my @arr = (1, 2, 3);
my $count = @arr; # 标量上下文: 元素个数 3
my @copy = @arr; # 列表上下文: 复制
my $first = @arr[0]; # 标量: 取一个
my @slice = @arr[0..1]; # 列表: 切片
# 常见上下文陷阱
my @list = (1, 2, 3);
my $n = @list; # 3 不是数组
print scalar @list; # 3 强制标量上下文
4. 变量插值
my $name = "张三";
my $age = 28;
# 双引号插值
print "姓名: $name, 年龄: $age\n";
# 消除歧义
print "姓名: ${name}先生\n";
# 数组插值
my @list = (1, 2, 3);
print "列表: @list\n"; # 1 2 3(以空格连接)
# 哈希插值
my %h = (a => 1);
print "%h\n"; # 哈希一般用 {} 访问
5. 环境变量与命令行参数
# 环境变量
print $ENV{HOME}; # 家目录
print $ENV{PATH};
# 命令行参数
# perl script.pl arg1 arg2
my $arg1 = $ARGV[0];
my $arg2 = $ARGV[1];
print "参数: $ARGV[0] $ARGV[1]\n";
# 遍历
foreach my $arg (@ARGV) {
print "$arg\n";
}
6. 常见特殊变量
$_ 默认变量
@_ 子程序参数数组
@ARGV 命令行参数
@INC 模块搜索路径
%ENV 环境变量
$0 程序名
$$ 进程 ID
$! 系统错误
$/ 输入行分隔符
$, 输出字段分隔符
$\ 输出行分隔符
$| 自动刷新缓冲区
Perl 标量
Perl 标量(Scalar)
标量是 Perl 中最基础的数据类型,保存单个值:数字或字符串。
1. 数字标量
my $int = 42;
my $float = 3.14;
my $negative = -10;
my $sci = 1.5e3; # 1500
# 数字格式
my $hex = 0xFF; # 255
my $oct = 0755; # 八进制
my $bin = 0b1010; # 二进制 10
# 大数字分隔
my $big = 1_000_000; # 1000000
2. 字符串标量
my $s1 = '单引号';
my $s2 = "双引号";
my $s3 = qq{qq 双引号};
my $s4 = q{单引号};
# 字符串操作
my $a = "Hello";
my $b = "World";
my $c = $a . " " . $b; # 拼接
print $c x 2, "\n"; # 重复 2 次
# 常用函数
length("hello"); # 5
lc("HELLO"); # 小写
uc("hello"); # 大写
ucfirst("hello"); # 首字母大写
chomp($s); # 去掉末尾换行
chop($s); # 去掉末尾字符
substr("hello", 1, 3); # "ell"
index("hello", "l"); # 2 位置
rindex("hello", "l"); # 3 最后位置
3. 数字 vs 字符串
# 自动转换(Perl 特性)
my $n = "10" + "20"; # 30(数字上下文)
my $s = "abc" . 123; # "abc123"(字符串上下文)
# 比较运算符不同!
# 数字比较: == != < > <= >=
# 字符串比较: eq ne lt gt le ge cmp
if (10 == "10") { print "数字相等\n"; }
if ("abc" eq "abc") { print "字符串相等\n"; }
# 太空船操作符
"a" cmp "b"; # -1
5 <=> 3; # 1
4. 真假值
# Perl 的假值: 0、""、"0"、undef、空列表
# 其他都是真值
my $x = 0;
if ($x) { } # 假
if ("0") { } # 假(字符串 0)
if ("0.0") { } # 真!
# undef: 未定义
my $y; # undef
if (defined $y) { } # 假
$y = 10;
if (defined $y) { } # 真
5. 数值格式化
# sprintf 格式化
my $pi = 3.14159;
printf "%.2f\n", $pi; # 3.14
printf "%d\n", 42; # 42
printf "%05d\n", 42; # 00042
printf "%x\n", 255; # ff
printf "%o\n", 8; # 10
printf "%-10s|\n", "left"; # 左对齐
# 转整数/浮点
int(3.99); # 3
"3.5" + 0; # 3.5
Perl 数组
Perl 数组(Array)
数组保存有序的标量列表,是 Perl 最常用的数据结构之一。
1. 创建数组
my @empty;
my @nums = (1, 2, 3);
my @strings = ("a", "b", "c");
my @mixed = (1, "two", 3.0);
# 范围操作符
my @range = (1..10); # 1 到 10
my @chars = ("a".."e"); # a b c d e
# 列表逗号
my @list = (1, 2, (3, 4), 5); # 展平 (1,2,3,4,5)
2. 访问与修改
my @arr = (10, 20, 30, 40);
$arr[0] # 10
$arr[3] # 40
$arr[-1] # 40 倒数第一个
$arr[-2] # 30
# 修改
$arr[1] = 99;
# 自动扩展
$arr[5] = 60; # 自动填充 undef
# 切片
my @slice = @arr[1..3]; # (99, 30, 40)
my @multi = @arr[0, 2]; # (10, 30)
3. 常用函数
my @arr = (1, 2, 3);
push @arr, 4; # 末尾添加 (1,2,3,4)
my $last = pop @arr; # 取出 4
unshift @arr, 0; # 开头添加 (0,1,2,3)
my $first = shift @arr; # 取出 0
# 其他
scalar @arr; # 长度
$#arr; # 最后索引
reverse @arr; # 反转
sort @arr; # 排序(字符串)
sort { $a <=> $b } @arr; # 数字排序
# splice: 通用修改
splice @arr, 1, 1; # 删除位置 1 的 1 个
splice @arr, 1, 0, "new"; # 位置 1 插入
# join / split
my $str = join ", ", @arr; # 数组→字符串
my @parts = split /,/, "a,b,c"; # 字符串→数组
4. 遍历数组
my @fruits = ("苹果", "香蕉", "橙子");
# foreach
foreach my $fruit (@fruits) {
print "$fruit\n";
}
# 简写(使用 $_)
for (@fruits) {
print "$_\n";
}
# 带索引
for my $i (0 .. $#fruits) {
print "$i: $fruits[$i]\n";
}
# while + each(数组)
while (my ($i, $v) = each @fruits) {
print "$i: $v\n";
}
5. 数组引用与嵌套
# 数组的数组(矩阵)
my @matrix = (
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
);
print $matrix[1][2]; # 6
print $matrix[0][0]; # 1
# 遍历
foreach my $row (@matrix) {
foreach my $cell (@$row) {
print "$cell ";
}
print "\n";
}
6. 数组去重
my @arr = (1, 2, 2, 3, 3, 3);
# 用哈希去重(保留第一次出现顺序)
my %seen;
my @unique = grep { !$seen{$_}++ } @arr;
print "@unique\n"; # 1 2 3
Perl 哈希
Perl 哈希(Hash)
哈希是键值对集合,键唯一,用于快速查找与统计。
1. 创建哈希
# 胖箭头方式(推荐)
my %person = (
name => "张三",
age => 28,
city => "北京",
);
# 逗号方式(成对)
my %colors = ("red", "#FF0000", "blue", "#0000FF");
# 匿名哈希引用
my $person_ref = {
name => "李四",
age => 30,
};
2. 访问与修改
my %h = (a => 1, b => 2);
$h{a} # 1
$h{b} # 2
# 修改/添加
$h{c} = 3;
$h{a} = 10;
# 不存在的键返回 undef
$h{z}; # undef
# 判断存在
if (exists $h{a}) { print "存在\n"; }
# 删除
delete $h{c};
# 清空
%h = ();
3. 遍历
my %h = (a => 1, b => 2, c => 3);
# 键值对
while (my ($k, $v) = each %h) {
print "$k = $v\n";
}
# 键
foreach my $k (keys %h) {
print "$k\n";
}
# 值
foreach my $v (values %h) {
print "$v\n";
}
# 注意: each 与 keys 的遍历顺序不固定
# 需要排序: sort keys %h
4. 哈希常用操作
my %h = (a => 1, b => 2, c => 3);
scalar keys %h; # 3 键个数
scalar values %h; # 3 值个数
# 按值排序
foreach my $k (sort { $h{$b} <=> $h{$a} } keys %h) {
print "$k: $h{$k}\n"; # 值降序
}
# 合并
my %h1 = (a => 1);
my %h2 = (b => 2, c => 3);
my %merged = (%h1, %h2); # 合并
5. 统计计数(经典)
my @words = ("apple", "banana", "apple", "orange", "banana", "apple");
my %count;
$count{$_}++ for @words;
foreach my $word (sort keys %count) {
print "$word: $count{$word}\n";
}
# apple: 3
# banana: 2
# orange: 1
6. 哈希的哈希(嵌套)
my %users = (
zhangsan => { age => 28, city => "北京" },
lisi => { age => 30, city => "上海" },
);
print $users{zhangsan}{age}; # 28
print $users{lisi}{city}; # 上海
# 遍历嵌套
foreach my $name (keys %users) {
print "$name: ";
while (my ($k, $v) = each %{ $users{$name} }) {
print "$k=$v ";
}
print "\n";
}
Perl 条件语句
Perl 条件语句
Perl 的 if/unless/三元运算符,以及修饰符写法(后缀)。
1. if / elsif / else
my $score = 85;
if ($score >= 90) {
print "优秀\n";
} elsif ($score >= 60) {
print "及格\n";
} else {
print "不及格\n";
}
2. unless(反条件)
# unless = if not
my $error = 0;
unless ($error) {
print "没有错误\n";
}
# 等价于
if (!$error) {
print "没有错误\n";
}
3. 语句修饰符(后缀写法)
# Perl 特色: 条件放句尾
print "OK\n" if $success;
print "错误\n" unless $success;
die "失败" unless $ok;
print "10" if $n == 10;
# 更简洁
my $result = $ok ? "成功" : "失败";
print $result;
4. 三元运算符
my $age = 20;
my $status = $age >= 18 ? "成年" : "未成年";
print "$status\n";
# 嵌套(不推荐,可读性差)
my $grade = $score >= 90 ? "A" :
$score >= 60 ? "B" : "C";
5. 逻辑运算
my $age = 25;
my $has_id = 1;
if ($age >= 18 && $has_id) {
print "允许进入\n";
}
# 短路特性
my $x = 0;
$x || die "x 为假"; # x 假时执行 die
# 常见模式: 默认值
my $name = $input || "默认名";
# 逻辑运算符
&& 与
|| 或
! 非
// defined-or(已定义才用)
my $value = undef;
my $default = $value // 100; # 100(undef 时)
6. 判断数值/字符串
# 数值判断
my $n = "abc";
if ($n =~ /^\d+$/) {
print "是数字\n";
}
# 判断文件
if (-e "file.txt") { print "文件存在\n"; }
if (-f "file.txt") { print "是文件\n"; }
if (-d "dir") { print "是目录\n"; }
7. given/when(实验性)
# 类似 switch(Perl 5.10+,需 use feature)
use feature 'say';
use feature 'switch';
my $day = 3;
given ($day) {
when (1) { say "周一" }
when (2) { say "周二" }
when (3) { say "周三" }
default { say "其他" }
}
# 注: 现代 Perl 更推荐用 dispatch 表或 if-elsif
Perl 循环
Perl 循环
Perl 提供 for、foreach、while、until 等循环,还有灵活的修饰符写法。
1. for / foreach
# for 与 foreach 可互换
for (my $i = 0; $i < 5; $i++) {
print "$i\n";
}
# C 风格
for (my $i = 0; $i < @arr; $i++) {
print "$arr[$i]\n";
}
# 范围
for my $i (1..5) {
print "$i\n";
}
# 遍历数组
foreach my $fruit ("苹果", "香蕉") {
print "$fruit\n";
}
# 默认变量 $_
for (1..3) {
print "$_\n"; # 1 2 3
}
2. while / until
# while: 条件为真时循环
my $count = 0;
while ($count < 5) {
print "$count\n";
$count++;
}
# until: 条件为假时循环(until = while not)
my $n = 0;
until ($n >= 3) {
print "$n\n";
$n++;
}
# 读取文件行(经典)
open my $fh, "<", "data.txt" or die $!;
while (my $line = <$fh>) {
chomp $line;
print "$line\n";
}
3. 语句修饰符循环
# Perl 特色: 单行循环
print "$_\n" for (1..3);
print "$_\n" foreach @arr;
$i++ while $i < 10;
# 无限循环 + last
while (1) {
print "无限\n";
last; # 退出
}
4. last / next / redo
# last: 退出循环(相当于 break)
for (1..10) {
last if $_ == 5; # 1 2 3 4
print "$_\n";
}
# next: 跳过本次(相当于 continue)
for (1..10) {
next if $_ % 2 == 0; # 只打印奇数
print "$_\n";
}
# redo: 重做本次
my $attempt = 0;
while (1) {
$attempt++;
if ($attempt < 3) {
redo; # 重新执行本次
}
last;
}
print "attempt = $attempt\n"; # 3
5. 循环标签
# 嵌套循环跳出外层
OUTER: for my $i (1..3) {
for my $j (1..3) {
next OUTER if $j == 2; # 跳到外层下一次
print "($i, $j)\n";
}
}
# 输出: (1,1) (2,1) (3,1)
6. 循环与哈希
my %count;
$count{$_}++ for ("a", "b", "a");
# 遍历键
for my $k (sort keys %count) {
print "$k: $count{$k}\n";
}
# while + each(不创建大列表,省内存)
while (my ($k, $v) = each %count) {
print "$k = $v\n";
}
Perl 运算符
Perl 运算符
Perl 运算符丰富,最独特的是数字与字符串两套比较运算符。
1. 算术运算符
+ - * / % **(幂) ++ --
my $sum = 10 + 5; # 15
my $diff = 10 - 5; # 5
my $prod = 10 * 5; # 50
my $quot = 10 / 3; # 3.333(浮点!)
my $mod = 10 % 3; # 1
my $pow = 2 ** 10; # 1024
# 自增自减
my $i = 0;
$i++; # 1
++$i; # 2(前缀先加后返回)
# 字符串自动转数字
"10" + "20"; # 30
2. 字符串运算符
. 拼接
x 重复
"Hello" . " " . "World"; # "Hello World"
"ab" x 3; # "ababab"
"-" x 20; # 分隔线
# 字符串重复
my $line = "=" x 30;
3. 比较运算符(两套!)
# 数字比较
== != < > <= >= <=>
# 字符串比较
eq ne lt gt le ge cmp
if (10 == "10") { } # 数字: 相等
if ("10" eq "10") { } # 字符串: 相等
if ("10" lt "9") { } # 字符串比较(字典序): 真!
if (10 < 9) { } # 数字比较: 假
# 飞船运算符(排序用)
5 <=> 3; # 1
3 <=> 5; # -1
3 <=> 3; # 0
"a" cmp "b"; # -1
4. 逻辑与位运算符
# 逻辑
&& || ! # 优先用这些(短路)
# 短路示例
$ok && do_something(); # ok 为真才执行
$err || die "失败"; # err 为假才 die
# 位运算
& | ^ ~ << >>
my $a = 0b1100;
my $b = 0b1010;
$a & $b; # 0b1000
$a | $b; # 0b1110
$a ^ $b; # 0b0110
$a << 1; # 左移
5. 赋值运算符
$x += 5; # 等价 $x = $x + 5
$x -= 5;
$x *= 2;
$x /= 2;
$x %= 3;
$x .= "str"; # 字符串追加
$x **= 2;
$x ||= "default"; # 假值才赋值
$x //= "default"; # undef 才赋值(常用)
# 经典模式
$count{$key} += 1; # 计数
$total //= 0; # 初始化
6. 引用与特殊运算符
\ 引用运算符
-> 解引用箭头
, 逗号(列表)
=> 胖箭头(哈希)
# 三元
$max = $a > $b ? $a : $b;
# 文件测试
-e 存在 -f 文件 -d 目录
-r 可读 -w 可写 -x 可执行
-z 空文件 -s 文件大小
# 范围
1..10 # 列表范围
'a'..'z'
Perl 时间日期
Perl 时间日期
Perl 提供 time、localtime、gmtime 等时间函数,还有 DateTime 模块。
1. 时间戳
# 当前时间戳(秒)
my $now = time();
print "$now\n";
# 格式化时间戳
my $local = localtime();
print "$local\n"; # Mon Aug 18 12:00:00 2026
my $utc = gmtime();
print "$utc\n"; # UTC 时间
2. localtime 列表
my @lt = localtime();
# (sec, min, hour, mday, mon, year, wday, yday, isdst)
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime();
$year += 1900; # 年份从 1900 起
$mon += 1; # 月份 0-11
print "$year-$mon-$mday $hour:$min:$sec\n";
# 2026-8-18 12:0:5
3. 格式化输出
# POSIX 函数
use POSIX qw(strftime);
my $ts = time();
my $date = strftime("%Y-%m-%d %H:%M:%S", localtime($ts));
print "$date\n"; # 2026-08-18 12:00:05
# 常用格式
strftime("%Y-%m-%d", localtime) # 2026-08-18
strftime("%H:%M:%S", localtime) # 12:00:05
strftime("%A", localtime) # Tuesday
strftime("%B", localtime) # August
strftime("%Y%m%d_%H%M%S", localtime) # 文件名友好
4. 时间计算
use POSIX qw(strftime);
# 加减秒
my $tomorrow = time() + 86400; # +1 天
my $next_hour = time() + 3600; # +1 小时
# 计算时间差
my $start = time();
# ... 执行任务 ...
my $elapsed = time() - $start;
print "耗时 $elapsed 秒\n";
# 特定日期
use Time::Local;
my $epoch = timelocal(0, 0, 12, 18, 7, 2026);
# 2026-08-18 12:00:00
5. DateTime 模块(推荐)
cpanm DateTime
use DateTime;
my $dt = DateTime->now;
print $dt->ymd, "\n"; # 2026-08-18
print $dt->hms, "\n"; # 12:00:05
# 创建特定时间
my $dt2 = DateTime->new(
year => 2026,
month => 8,
day => 18,
hour => 12,
);
print $dt2->strftime("%Y/%m/%d"), "\n";
# 计算
my $later = $dt->clone->add(days => 7);
my $diff = $later->subtract_datetime($dt);
print $diff->days, " 天\n"; # 7
# 时区
my $beijing = DateTime->now(time_zone => "Asia/Shanghai");
print $beijing->hms;
6. 常见应用
# 日志时间戳
use POSIX qw(strftime);
sub log_msg {
my ($msg) = @_;
my $ts = strftime("%Y-%m-%d %H:%M:%S", localtime);
print "[$ts] $msg\n";
}
log_msg("程序启动");
# 日期文件命名
my $filename = strftime("backup_%Y%m%d.tar.gz", localtime);
print "$filename\n";
Perl 子程序(函数)
Perl 子程序(Subroutine)
子程序是 Perl 的代码复用单元,参数通过 @_ 传递。
1. 定义与调用
# 定义
sub greet {
print "Hello!\n";
}
# 调用
greet();
greet; # 也可省略括号
# 带参数
sub say_hello {
my ($name) = @_;
print "你好, $name\n";
}
say_hello("张三");
# 提前定义或用 & 调用(不推荐)
&greet;
2. 参数处理
sub add {
my ($a, $b) = @_; # 标准方式: 解构 @_
return $a + $b;
}
print add(3, 5), "\n"; # 8
# 多参数
sub max {
my $max = shift @_; # shift 取出第一个
foreach my $n (@_) {
$max = $n if $n > $max;
}
return $max;
}
print max(3, 7, 5, 9), "\n"; # 9
# 数组与哈希参数
sub process {
my ($name, @args) = @_; # 第一个是标量,其余数组
my ($name, %opts) = @_; # 或第一个标量,其余哈希
}
3. 返回值
# 返回标量
sub square { return $_[0] ** 2; }
my $s = square(5); # 25
# 返回列表
sub get_pair {
return (1, 2);
}
my ($a, $b) = get_pair(); # 1 2
# 返回哈希
sub get_config {
return (name => "app", version => "1.0");
}
my %config = get_config();
# 没有 return: 返回最后一个表达式
sub add2 { $_[0] + $_[1] }
# 上下文敏感返回
sub get_list {
return (1, 2, 3);
}
my @list = get_list(); # 列表上下文 (1,2,3)
my $n = get_list(); # 标量上下文 3(个数!)
4. 原型(Prototypes)
# 原型限制参数
sub add(\$\$) { } # 两个标量引用
sub my_push(\@@) { } # 数组引用 + 列表
# 但原型限制多,现代 Perl 推荐签名
use feature 'signatures'; # Perl 5.36+ 默认
sub greet($name) {
print "你好, $name\n";
}
sub add($a, $b) {
return $a + $b;
}
# 签名特性
use feature 'signatures';
sub config($name, $version = "1.0") { # 默认值
...
}
5. 递归
sub factorial {
my ($n) = @_;
return 1 if $n <= 1;
return $n * factorial($n - 1);
}
print factorial(5), "\n"; # 120
# 斐波那契(带缓存)
my %cache;
sub fib {
my ($n) = @_;
return $n if $n < 2;
return $cache{$n} //= fib($n-1) + fib($n-2);
}
print fib(30), "\n";
6. 子程序引用
# 匿名子程序
my $func = sub {
my ($x) = @_;
return $x * 2;
};
print $func->(5), "\n"; # 10
# 回调
sub apply {
my ($func, $value) = @_;
return $func->($value);
}
print apply(sub { $_[0] ** 2 }, 4), "\n"; # 16
# map / grep 中使用
my @nums = (1, 2, 3, 4);
my @doubled = map { $_ * 2 } @nums;
my @evens = grep { $_ % 2 == 0 } @nums;
Perl 引用
Perl 引用(Reference)
引用是指向数据的指针,用于构建复杂数据结构与传参。
1. 创建引用
my @arr = (1, 2, 3);
my $arr_ref = \@arr; # 数组引用
my %hash = (a => 1);
my $hash_ref = \%hash; # 哈希引用
my $scalar = 5;
my $scalar_ref = \$scalar; # 标量引用
# 匿名引用(推荐)
my $arr_ref2 = [1, 2, 3]; # 匿名数组
my $hash_ref2 = { name => "张三" }; # 匿名哈希
my $code_ref = sub { "匿名子程序" }; # 匿名子程序
2. 解引用
my $arr_ref = [1, 2, 3];
my $hash_ref = { name => "张三" };
# 完整解引用
my @arr = @$arr_ref; # 整个数组
my %hash = %$hash_ref; # 整个哈希
# 元素访问
$arr_ref->[0]; # 1
$hash_ref->{name}; # "张三"
# 或
$$arr_ref[0];
$$hash_ref{name};
# 多层
my $data = { users => [ { name => "张三" } ] };
$data->{users}[0]{name}; # "张三"
3. 引用类型判断
my $arr = [1, 2];
my $hash = {a => 1};
my $code = sub { };
ref($arr); # ARRAY
ref($hash); # HASH
ref($code); # CODE
ref("str"); # 空字符串(非引用)
# 根据类型处理
sub process_ref {
my ($ref) = @_;
my $type = ref($ref);
if ($type eq "HASH") {
print "键: ", join(", ", keys %$ref), "\n";
} elsif ($type eq "ARRAY") {
print "元素: ", scalar @$ref, " 个\n";
}
}
4. 传引用给函数(避免复制)
# 大数组传引用高效
sub sum {
my ($arr_ref) = @_;
my $total = 0;
$total += $_ for @$arr_ref;
return $total;
}
my @big = (1..100000);
print sum(\@big), "\n";
# 修改原数据
sub add_to_first {
my ($arr_ref) = @_;
$arr_ref->[0] += 100;
}
my @nums = (1, 2, 3);
add_to_first(\@nums);
print "@nums\n"; # 101 2 3
5. 复杂数据结构
# 数组的数组(矩阵)
my $matrix = [
[1, 2, 3],
[4, 5, 6],
];
print $matrix->[1][2]; # 6
# 哈希的数组
my $people = [
{ name => "张三", age => 28 },
{ name => "李四", age => 30 },
];
foreach my $p (@$people) {
print "$p->{name}: $p->{age}\n";
}
# 哈希的哈希
my $scores = {
math => { zhangsan => 90, lisi => 85 },
english => { zhangsan => 88, lisi => 92 },
};
print $scores->{math}{zhangsan}; # 90
6. 序列化与反序列化
# 存储到文件/传递
use Storable; # 内置
my $data = { name => "张三", scores => [1, 2, 3] };
# 序列化到字符串
my $frozen = Storable::freeze($data);
# 反序列化
my $copy = Storable::thaw($frozen);
# 存文件
Storable::store($data, "data.stor");
my $loaded = Storable::retrieve("data.stor");
# JSON(常用于 API)
use JSON::PP;
my $json = encode_json($data);
my $back = decode_json($json);
Perl 格式化输出
Perl 格式化输出
Perl 的 printf/sprintf 与 format 语句用于生成对齐的报表。
1. printf 格式化
# printf: 直接输出
printf "%s 今年 %d 岁\n", "张三", 28;
# sprintf: 返回字符串
my $str = sprintf "%s-%d", "v", 1;
print "$str\n"; # v-1
# 常用格式
%s 字符串
%d 整数
%f 浮点
%e 科学计数
%x 十六进制
%o 八进制
%b 二进制
%% 百分号
2. 宽度与对齐
# 宽度与对齐
printf "|%10s|\n", "right"; # 右对齐
printf "|%-10s|\n", "left"; # 左对齐
printf "|%10d|\n", 42;
printf "|%-10d|\n", 42;
# 补零
printf "%05d\n", 42; # 00042
printf "%08.2f\n", 3.14; # 00003.14
# 浮点精度
printf "%.2f\n", 3.14159; # 3.14
printf "%.0f\n", 3.5; # 4(四舍五入)
3. 表格输出
# 对齐列
my @rows = (
["张三", 28, 90.5],
["李四", 30, 85.0],
["王五", 25, 95.5],
);
printf "%-8s %4s %8s\n", "姓名", "年龄", "分数";
printf "%-8s %4s %8s\n", "-" x 8, "-" x 4, "-" x 8;
foreach my $row (@rows) {
printf "%-8s %4d %8.1f\n", @$row;
}
# 输出:
# 姓名 年龄 分数
# -------- ---- --------
# 张三 28 90.5
# 李四 30 85.0
# 王五 25 95.5
4. format 报表
# Perl 传统 format(类似 awk 风格)
my $name = "张三";
my $age = 28;
format STDOUT =
姓名: @<<<<<< 年龄: @## 状态: @<<<
$name, $age, "在职"
.
write;
# 字段格式:
# @<<<<<< 左对齐字符串
# @>>>>>> 右对齐字符串
# @|||||| 居中对齐
# @#### 右对齐数字
# @<<<@<<< 多字段
# 表格 format
format REPORT =
=====================
@<<<<<<< @####.##
$name, $price
.
$~ = "REPORT";
write;
5. 输出到文件
open my $fh, ">", "report.txt" or die $!;
printf $fh "%-8s %4s\n", "姓名", "分数";
printf $fh "%-8s %4d\n", "张三", 90;
# 或 select 切换默认句柄
select $fh;
$~ = "REPORT";
write;
select STDOUT;
close $fh;
6. 常见技巧
# 数字千分位
sub format_num {
my ($n) = @_;
my $str = reverse $n;
$str =~ s/(\d{3})(?=\d)/$1,/g;
return reverse $str;
}
print format_num(1234567), "\n"; # 1,234,567
# 日期格式化(见时间日期章节)
# 输出 JSON 格式化
use JSON::PP;
my $pretty = JSON::PP->new->pretty->encode($data);
Perl 文件操作
Perl 文件操作
Perl 的文件操作简单强大,配合正则可以做各种文本处理。
1. 打开文件
# 读取
open my $fh, "<", "data.txt" or die "打开失败: $!";
# 写入(覆盖)
open my $fh, ">", "out.txt" or die $!;
# 追加
open my $fh, ">>", "log.txt" or die $!;
# 读写
open my $fh, "+<", "data.txt" or die $!;
# 编码(Perl 5.16+)
open my $fh, "<:encoding(UTF-8)", "data.txt" or die $!;
open my $fh, ">:encoding(UTF-8)", "out.txt" or die $!;
2. 读取文件
# 逐行读取(推荐,内存友好)
open my $fh, "<", "data.txt" or die $!;
while (my $line = <$fh>) {
chomp $line;
print "行: $line\n";
}
close $fh;
# 一次性读取全部
my $content = do { local $/; open my $fh, "<", "data.txt" or die $!; <$fh> };
# 读入数组
my @lines = <$fh>; # 每行一个元素(带换行)
# 读取固定字节
read $fh, my $buf, 1024;
3. 写入文件
open my $fh, ">", "out.txt" or die $!;
print $fh "第一行\n";
print $fh "第二行\n";
# 或
say $fh "第三行"; # 需 use feature 'say';
close $fh;
# 快速写
use File::Slurp qw(write_file);
write_file("out.txt", "内容");
# 快速读
use File::Slurp qw(read_file);
my $content = read_file("data.txt");
4. 文件测试
# 常用文件测试操作符
-e "file.txt" 存在
-f "file.txt" 是普通文件
-d "dir" 是目录
-r "file" 可读
-w "file" 可写
-x "file" 可执行
-z "file" 空文件
-s "file" 文件大小
-M "file" 修改时间(天前)
if (-e "config.ini") {
print "配置存在\n";
}
# 组合
if (-f $file && -r $file) {
print "可读的普通文件\n";
}
5. 文件操作函数
rename "old.txt", "new.txt"; # 重命名
unlink "temp.txt"; # 删除
copy 文件: use File::Copy qw(copy move);
copy("a.txt", "b.txt");
move("a.txt", "dir/a.txt");
# 文件信息
my $size = -s "data.txt";
my $mtime = (stat "data.txt")[9]; # 修改时间
# 时间转换
use POSIX qw(strftime);
my $date = strftime("%Y-%m-%d", localtime($mtime));
6. 文本处理实战
# 统计行数/单词数
open my $fh, "<", "data.txt" or die $!;
my ($lines, $words) = (0, 0);
while (<$fh>) {
$lines++;
$words += split /\s+/;
}
close $fh;
print "行数: $lines, 单词数: $words\n";
# 替换文件内容
local $/; # 读取整个文件
open my $fh, "<", "data.txt" or die $!;
my $content = <$fh>;
close $fh;
$content =~ s/old/new/g;
open my $out, ">", "data.txt" or die $!;
print $out $content;
close $out;
# 原地修改
perl -i.bak -pe 's/foo/bar/g' data.txt
Perl 目录操作
Perl 目录操作
Perl 通过 opendir/readdir 或 File::Find 处理目录与遍历。
1. 打开与读取目录
# 列出目录内容
opendir my $dh, "." or die $!;
my @files = readdir $dh;
closedir $dh;
foreach my $f (sort @files) {
print "$f\n";
}
# 包含 . 和 ..
# 排除 . ..
my @real = grep { $_ ne "." && $_ ne ".." } readdir $dh;
2. 只列出文件/目录
opendir my $dh, "." or die $!;
my @names = readdir $dh;
closedir $dh;
# 只留文件
my @files = grep { -f $_ } @names;
# 只留目录
my @dirs = grep { -d $_ } @names;
# 过滤扩展名
my @txt = grep { /\.txt$/ } @names;
# 更清晰: 完整路径
foreach my $name (@names) {
my $path = "./$name";
next unless -f $path;
print "$path\n";
}
3. 创建与删除目录
mkdir "new_dir"; # 创建
mkdir "a/b/c", 0755; # 注意: 只创建一层
# 递归创建
use File::Path qw(make_path remove_tree);
make_path("a/b/c");
# 删除
rmdir "empty_dir"; # 只删空目录
remove_tree("a"); # 递归删除
# 检查
if (-d "dir") { print "存在\n"; }
4. 递归遍历(File::Find)
use File::Find;
my @all_files;
find(sub {
# $_ 是文件名, $File::Find::name 是完整路径
push @all_files, $File::Find::name if -f;
}, ".");
print "共 " . scalar @all_files . " 个文件\n";
# 查找大文件
find(sub {
my $size = -s;
if ($size > 1_000_000) { # > 1MB
print "$File::Find::name: $size 字节\n";
}
}, ".");
5. 备份与归档
# 批量重命名
opendir my $dh, "." or die $!;
my @files = grep { /\.jpg$/ } readdir $dh;
closedir $dh;
foreach my $f (@files) {
my $new = $f;
$new =~ s/\.jpg$/\.JPG/;
rename $f, $new;
}
# 压缩归档
system "tar czf backup.tar.gz mydir";
system "zip -r backup.zip mydir";
# 或 Perl 方式
use Archive::Tar;
my $tar = Archive::Tar->new;
$tar->add_files("a.txt", "b.txt");
$tar->write("backup.tar");
6. 路径处理
use File::Basename;
use File::Spec;
my $path = "/tmp/data/file.txt";
basename($path); # file.txt
dirname($path); # /tmp/data
fileparse($path); # ("file.txt", "/tmp/data/")
# 拼接路径(跨平台)
my $full = File::Spec->catfile("dir", "sub", "file.txt");
print "$full\n";
# 当前目录
use Cwd qw(getcwd);
print getcwd, "\n";
Perl 错误处理
Perl 错误处理
Perl 使用 die/eval/Try::Tiny 处理错误,习惯与异常语言不同。
1. die 与 $!
# die: 终止程序并输出错误
open my $fh, "<", "missing.txt" or die "无法打开: $!";
# $! 保存系统错误信息
# 自定义错误
die "参数无效\n";
# 更友好的退出
die "错误信息" if $condition;
exit 1 if $condition; # 仅退出码
# 检查返回值
my $result = risky_op() or die "操作失败";
2. eval 捕获错误
# eval: 捕获 die(类似 try)
eval {
die "发生错误";
# 其他危险操作
};
if ($@) { # $@ 保存错误信息
print "捕获到: $@";
}
# 处理失败恢复
eval {
open my $fh, "<", "data.txt" or die "打开失败: $!";
# 读取处理...
};
if ($@) {
warn "读取失败: $@, 使用默认数据";
$data = "default";
}
3. warn 警告
# warn: 输出警告但不终止
warn "警告: 配置缺失,使用默认值\n";
# 条件警告
warn "文件不存在" unless -e "config.ini";
# 带调用位置
warn "问题发生在第 $.(?) 行";
# 自定义警告处理
local $SIG{__WARN__} = sub {
my ($msg) = @_;
open my $log, ">>", "warnings.log" or die $!;
print $log $msg;
close $log;
};
4. 错误对象(Carp)
# Carp: 从调用者角度报错
use Carp qw(carp croak confess);
carp "警告信息"; # 从调用者行号报错
croak "致命错误"; # 从调用者行号终止
confess "详细堆栈"; # 打印调用栈
# 库开发推荐使用
sub process {
my ($data) = @_;
croak "参数必须是引用" unless ref $data;
}
5. Try::Tiny 模块(推荐)
cpanm Try::Tiny
use Try::Tiny;
try {
risky_operation();
} catch {
# $_ 是错误信息
warn "捕获错误: $_";
} finally {
# 无论成败都执行
cleanup();
};
# 返回值
my $result = try {
compute();
} catch {
"默认值"; # 出错时返回
};
6. 最佳实践
1. use strict; use warnings; 必须开
2. 打开文件/数据库等资源用 or die
3. 可能失败的库调用处理返回值
4. 用 eval/Try::Tiny 隔离可恢复错误
5. 库代码用 Carp::croak 报告错误
6. 关键路径记录日志(Log::Log4perl)
7. 不要用裸 $@ 判断(会踩空)
# 日志模块
use Log::Log4perl qw(get_logger);
Log::Log4perl->easy_init($Log::Log4perl::INFO);
my $log = get_logger();
$log->info("开始任务");
$log->error("出错了: $!");
Perl 特殊变量
Perl 特殊变量
Perl 有大量内置特殊变量,掌握常用几个事半功倍。
1. 默认变量 $_
# $_ 是许多函数的默认参数
$_ = "hello";
print; # 默认打印 $_
print "$_\n";
# 循环中自动设置
for (1..3) {
print "$_\n"; # 1 2 3
}
# grep/map 默认
my @evens = grep { $_ % 2 == 0 } (1..10);
my @doubled = map { $_ * 2 } (1..3);
# while(<>) 文件读取
while (<>) { # 读入到 $_
print; # 打印
}
2. 输入/输出相关
# $/: 输入行分隔符(默认 \n)
local $/ = undef; # 读整个文件
my $all = <$fh>;
# $\: 输出行分隔符
local $\ = "\n";
print "a", "b"; # 自动换行
# $,: 输出字段分隔符
local $, = ", ";
print @array; # 元素逗号分隔
# $|: 自动刷新缓冲区
$| = 1; # 立即输出(管道时重要)
# $.: 当前输入行号
while (<>) {
print "第 $. 行: $_";
}
3. 参数与进程
@ARGV 命令行参数
$0 程序名
$$ 当前进程 ID
$? 子进程退出状态
$! 系统错误信息
$@ eval 错误信息
# 命令行参数处理
my $script = $0;
my @args = @ARGV;
print "程序: $script, PID: $$\n";
foreach my $arg (@ARGV) {
print "参数: $arg\n";
}
# 退出状态
system("ls /tmp");
print "退出码: $?\n"; # 128 的倍数+信号
4. 正则相关
$& 上次匹配的字符串
$` 匹配之前的
$' 匹配之后的
$1 $2 捕获组
@+ 各捕获结束位置
"hello world" =~ /(\w+) (\w+)/;
print "$1 $2\n"; # hello world
# 全局匹配后
"abc" =~ /b/;
print "$` $& $'\n"; # a b c
5. 数组相关
@_ 子程序参数
@ARGV 命令行参数
@INC 模块搜索路径
@+ 正则捕获位置
$[ 数组起始索引(默认 0,勿改)
# 子程序中
sub sum {
my $total = 0;
$total += $_ for @_;
return $total;
}
print sum(1, 2, 3), "\n"; # 6
6. 其他常用特殊变量
%ENV 环境变量
$ENV{PATH}
$ENV{HOME}
$^I 原地编辑备份后缀
perl -i.bak -pe 's/x/y/' file
$^V Perl 版本
print "$^V\n"; # v5.38.2
$^W 警告开关
$] Perl 版本号(数字)
# 修改环境变量
$ENV{PATH} = "/usr/bin:$ENV{PATH}";
delete $ENV{SECRET};
Perl 正则表达式
Perl 正则表达式
正则表达式是 Perl 的灵魂,内建于语言中,处理文本效率极高。
1. 匹配(=~ m//)
my $str = "Hello World 2026";
# 匹配
if ($str =~ /World/) {
print "包含 World\n";
}
# 忽略大小写 i
if ($str =~ /world/i) {
print "忽略大小写匹配\n";
}
# 不匹配 !~
if ($str !~ /foo/) {
print "不包含 foo\n";
}
# 匹配并捕获
if ($str =~ /(\w+) (\w+)/) {
print "$1 $2\n"; # Hello World
}
# 绑定到默认变量
$_ = "abc123";
if (/\d+/) {
print "含数字: $&\n"; # 123
}
2. 元字符
. 任意字符(除换行)
\d 数字 \D 非数字
\w 单词字符 \W 非单词
\s 空白 \S 非空白
\b 单词边界 \B 非边界
^ 行首 $ 行尾
\n 换行 \t 制表
# 示例
"abc123" =~ /\d+/; # 123
"hello" =~ /^h/; # 行首 h
"hello" =~ /o$/; # 行尾 o
"a b" =~ /\s/; # 空白
3. 量词
* 0 次或多次
+ 1 次或多次
? 0 次或 1 次
{n} 恰好 n 次
{n,} 至少 n 次
{n,m} n 到 m 次
# 贪婪 vs 懒惰
"aaa" =~ /a+/; # 贪婪: 全部 aaa
"aaa" =~ /a+?/; # 懒惰: 只匹配 a
"xy" =~ /<.*>/; # 贪婪: 整行
"xy" =~ /<.*?>/; # 懒惰:
4. 分组与交替
# 分组 ()
"hello world" =~ /(hello) (world)/; # $1 $2
# 交替 |
"cat" =~ /cat|dog/; # 匹配 cat
"dog" =~ /cat|dog/; # 匹配 dog
# 非捕获组 (?:)
"abc" =~ /(?:ab)c/; # 不设 $1
# 命名捕获
"2026-08-18" =~ /(?\d{4})-(?\d{2})/;
print "$+{year}-$+{mon}\n"; # 2026-08
# 反向引用 \1
"ab ab" =~ /(\w+) \1/; # 匹配重复词
5. 字符类
[abc] a/b/c 之一
[a-z] 小写字母
[0-9] 数字
[^a-z] 非小写字母
[\d_] 数字或下划线
# 示例
"abc" =~ /[a-c]/; # 匹配
"123" =~ /[0-9]{3}/; # 三个数字
# POSIX 类
[[:alpha:]] 字母
[[:digit:]] 数字
[[:space:]] 空白
[[:alnum:]] 字母数字
6. 替换与分割
# 替换 s///
my $s = "Hello World";
$s =~ s/World/Rust/;
print "$s\n"; # Hello Rust
# 全局 g
$s =~ s/o/O/g;
print "$s\n"; # HellO WOrld
# 忽略大小写 i
$s =~ s/hello/HI/i;
# 保留原值(副本)
(my $new = $s) =~ s/Hello/Hi/;
# 分割 split
my @parts = split /[,\s]+/, "a, b, c";
# ("a", "b", "c")
# 连接 join(非正则)
my $str = join "-", @parts;
7. 修饰符
i 忽略大小写
g 全局匹配
m 多行模式(^ $ 匹配每行)
s 单行模式(. 匹配换行)
x 扩展模式(允许空格注释)
u Unicode
# 多行
my $text = "line1\nline2";
$text =~ /^line2/m; # 匹配
# 忽略空白与注释
$text =~ /
\d{4} # 年份
-\d{2} # 月份
-\d{2} # 日
/x;
Perl 发送邮件
Perl 发送邮件
Perl 通过 Email::Sender、Net::SMTP 等模块发送邮件。
1. 简单方式(命令行)
# 利用系统 sendmail
open my $mail, "|", "/usr/sbin/sendmail -t" or die $!;
print $mail "To: receiver\@example.com\n";
print $mail "From: sender\@example.com\n";
print $mail "Subject: 测试邮件\n";
print $mail "\n";
print $mail "邮件正文内容\n";
close $mail;
2. Net::SMTP 模块
cpanm Net::SMTP
# 或 Net::SMTP 是核心模块
use Net::SMTP;
my $smtp = Net::SMTP->new("smtp.example.com",
Port => 25,
Timeout => 30,
) or die "连接失败: $!";
# 认证
$smtp->auth("username", "password");
# 邮件信息
$smtp->mail("sender\@example.com");
$smtp->to("receiver\@example.com");
$smtp->data();
$smtp->datasend("Subject: 测试\n");
$smtp->datasend("\n");
$smtp->datasend("这是邮件正文\n");
$smtp->dataend();
$smtp->quit;
3. MIME::Lite(常用)
cpanm MIME::Lite
use MIME::Lite;
my $msg = MIME::Lite->new(
From => "sender\@example.com",
To => "receiver\@example.com",
Cc => "cc\@example.com",
Subject => "测试邮件",
Type => "multipart/mixed",
);
# 文本正文
$msg->attach(
Type => "text/plain",
Data => "邮件正文内容",
);
# HTML 正文
$msg->attach(
Type => "text/html",
Data => "标题
内容
",
);
# 附件
$msg->attach(
Type => "application/octet-stream",
Path => "report.pdf",
Filename => "report.pdf",
);
# 发送
$msg->send(
"smtp", "smtp.example.com",
AuthUser => "user",
AuthPass => "pass",
);
4. Email::Sender(现代)
cpanm Email::Sender Email::Simple
use Email::Sender::Simple qw(sendmail);
use Email::Simple;
use Email::Simple::Creator;
my $email = Email::Simple->create(
header => [
From => "sender\@example.com",
To => "receiver\@example.com",
Subject => "测试邮件",
],
body => "邮件正文",
);
sendmail($email);
5. 群发与错误处理
use MIME::Lite;
my @recipients = qw(a@example.com b@example.com);
foreach my $to (@recipients) {
my $msg = MIME::Lite->new(
From => "sender\@example.com",
To => $to,
Subject => "通知",
Data => "亲爱的用户...",
);
eval {
$msg->send("smtp", "smtp.example.com", ...);
print "已发送给 $to\n";
};
if ($@) {
warn "发送失败 $to: $@";
}
}
6. 注意事项
1. SMTP 认证信息不要硬编码(用环境变量)
2. 发送频率限制,防被当作垃圾邮件
3. 生产环境用异步/队列发送
4. 中文主题用 =?UTF-8?B? 编码(Encode::MIME::Header)
5. 附件大小限制
Perl Socket 编程
Perl Socket 编程
Perl 通过 IO::Socket 模块轻松实现 TCP/UDP 网络编程。
1. TCP 客户端
use IO::Socket::INET;
my $socket = IO::Socket::INET->new(
PeerAddr => "example.com",
PeerPort => 80,
Proto => "tcp",
Timeout => 10,
) or die "连接失败: $!";
# 发送请求
print $socket "GET / HTTP/1.0\r\n\r\n";
# 接收响应
while (my $line = <$socket>) {
print $line;
}
close $socket;
2. TCP 服务器
use IO::Socket::INET;
my $server = IO::Socket::INET->new(
LocalAddr => "0.0.0.0",
LocalPort => 8080,
Proto => "tcp",
Listen => 10,
ReuseAddr => 1,
) or die "无法创建服务器: $!";
print "监听 8080 端口...\n";
while (my $client = $server->accept()) {
my $peer = $client->peerhost();
print "来自 $peer 的连接\n";
while (my $line = <$client>) {
print "收到: $line";
print $client "Echo: $line";
last if $line =~ /^quit/i;
}
close $client;
}
close $server;
3. 多进程并发服务器
use IO::Socket::INET;
use POSIX qw(fork);
my $server = IO::Socket::INET->new(
LocalPort => 8080, Proto => "tcp",
Listen => 10, ReuseAddr => 1,
) or die $!;
print "服务器启动\n";
while (my $client = $server->accept()) {
my $pid = fork();
if ($pid == 0) { # 子进程
# 处理请求
while (my $line = <$client>) {
print $client "Reply: $line";
}
close $client;
exit 0;
}
close $client; # 父进程关闭副本
}
4. UDP 编程
use IO::Socket::INET;
# UDP 服务器
my $udp_server = IO::Socket::INET->new(
LocalPort => 8888,
Proto => "udp",
) or die $!;
my $data = "";
$udp_server->recv($data, 1024);
print "收到: $data\n";
# UDP 客户端
my $udp_client = IO::Socket::INET->new(
PeerAddr => "127.0.0.1",
PeerPort => 8888,
Proto => "udp",
);
$udp_client->send("Hello UDP");
5. HTTP 客户端
# LWP::Simple 简单请求
cpanm LWP::Simple
use LWP::Simple;
my $content = get("http://example.com");
if (defined $content) {
print substr($content, 0, 100), "\n";
}
# 带请求头
use LWP::UserAgent;
my $ua = LWP::UserAgent->new(timeout => 10);
$ua->default_header("User-Agent" => "Perl Agent");
my $resp = $ua->get("http://example.com");
if ($resp->is_success) {
print $resp->decoded_content, "\n";
} else {
print "失败: ", $resp->status_line, "\n";
}
6. 安全注意
1. 服务器要处理并发与超时
2. 输入校验防注入
3. 不要 bind 到 0.0.0.0 若无需要
4. 关闭不需要的端口
5. 日志记录连接来源
6. 生产环境用成熟框架(Mojolicious 等)
Perl 面向对象
Perl 面向对象
Perl 的 OOP 基于包与 bless,简单灵活;Mo 等模块让 OOP 更现代。
1. 传统 Perl OOP
# 包即类
package Person;
sub new {
my ($class, %args) = @_;
my $self = { name => $args{name}, age => $args{age} };
bless $self, $class; # 关联类
return $self;
}
sub greet {
my ($self) = @_;
return "你好, 我是 $self->{name}";
}
sub get_age {
my ($self) = @_;
return $self->{age};
}
1; # 包必须返回真值
# 使用
my $p = Person->new(name => "张三", age => 28);
print $p->greet(), "\n";
print "年龄: ", $p->get_age(), "\n";
2. 继承
package Student;
use parent "Person"; # 继承
sub new {
my ($class, %args) = @_;
my $self = $class->SUPER::new(%args); # 调父类
$self->{school} = $args{school};
return $self;
}
sub study {
my ($self) = @_;
return "$self->{name} 在 $self->{school} 学习";
}
# 覆盖父类方法
sub greet {
my ($self) = @_;
return "同学你好, 我是 $self->{name}";
}
1;
# 使用
my $s = Student->new(
name => "李四", age => 20, school => "北大"
);
print $s->greet(), "\n"; # 覆盖版本
print $s->study(), "\n";
3. 封装(访问控制)
package Account;
sub new {
my ($class, $balance) = @_;
my $self = { balance => $balance };
bless $self, $class;
return $self;
}
# 公开方法
sub deposit {
my ($self, $amount) = @_;
$self->{balance} += $amount if $amount > 0;
}
sub get_balance {
my ($self) = @_;
return $self->{balance};
}
# "私有"方法(约定: 以下划线开头)
sub _validate {
my ($self) = @_;
return $self->{balance} > 0;
}
1;
4. Mo/Moo(现代 OOP)
cpanm Moo
package Person;
use Moo;
# 属性(自动生成 getter/setter)
has name => (is => "ro", required => 1); # 只读
has age => (is => "rw", default => 0); # 读写
# 方法
sub greet {
my ($self) = @_;
return "你好, $self->{name}";
}
1;
# 使用
my $p = Person->new(name => "张三");
print $p->name, "\n"; # 张三
$p->age(30);
print $p->age, "\n"; # 30
# 类型约束
has age => (
is => "rw",
isa => sub { die "必须数字" unless $_[0] =~ /^\d+$/ },
);
# 继承
package Student;
use Moo;
extends "Person";
has school => (is => "ro");
1;
5. 角色(Role)复用
cpanm Role::Tiny
package GreetRole;
use Role::Tiny;
requires qw(name); # 要求实现
sub greet {
my ($self) = @_;
return "你好, $self->{name}";
}
1;
package Person;
use Moo;
use Role::Tiny::With;
has name => (is => "ro");
with "GreetRole"; # 混入角色
1;
my $p = Person->new(name => "张三");
print $p->greet(), "\n"; # 你好, 张三
6. 最佳实践
1. 新代码用 Moo/Moose(比裸 bless 更健壮)
2. 属性声明 is/isa/default
3. 用角色复用行为,避免深继承
4. 文件命名与包对应(Person.pm)
5. 每个包文件结尾 1;
6. 模块目录: lib/Person.pm,use lib 'lib';
Perl 数据库连接
Perl 数据库连接
Perl 通过 DBI(DataBase Interface)统一操作各种数据库。
1. 安装 DBI 与驱动
cpanm DBI
cpanm DBD::mysql # MySQL
cpanm DBD::Pg # PostgreSQL
cpanm DBD::SQLite # SQLite(最简)
cpanm DBD::Oracle # Oracle
2. 连接数据库
use DBI;
# MySQL
my $dbh = DBI->connect(
"DBI:mysql:database=shop;host=127.0.0.1;port=3306",
"root",
"password",
{ RaiseError => 1, AutoCommit => 1, mysql_enable_utf8 => 1 },
) or die "连接失败: $DBI::errstr";
# SQLite(无认证)
my $dbh = DBI->connect(
"DBI:SQLite:dbname=data.db",
"", "",
{ RaiseError => 1 },
) or die $DBI::errstr;
# PostgreSQL
# DBI:pg:dbname=shop;host=127.0.0.1
3. 查询数据
# prepare + execute
my $sth = $dbh->prepare("SELECT id, name, price FROM products");
$sth->execute();
# 逐行取
while (my $row = $sth->fetchrow_hashref) {
print "$row->{id}: $row->{name} - $row->{price}\n";
}
# 取数组
$sth->execute();
while (my @row = $sth->fetchrow_array) {
print "@row\n";
}
# 一次性全取
my $rows = $dbh->selectall_arrayref(
"SELECT * FROM products",
{ Slice => {} }, # 哈希行
);
foreach my $r (@$rows) {
print "$r->{name}\n";
}
$sth->finish;
4. 预处理语句(防注入)
# ? 占位符
my $sth = $dbh->prepare(
"SELECT * FROM products WHERE price > ? AND category = ?"
);
$sth->execute(50, "books");
# 命名占位符
my $sth = $dbh->prepare(
"SELECT * FROM products WHERE name LIKE :kw"
);
$sth->bind_param(":kw", "%{$kw}%");
$sth->execute();
# 插入
my $sth = $dbh->prepare(
"INSERT INTO products (name, price) VALUES (?, ?)"
);
$sth->execute("新商品", 19.9);
print "插入 ID: ", $dbh->last_insert_id("", "", "products", "");
5. 更新与删除
# do 快速执行
my $rows = $dbh->do(
"UPDATE products SET price = price * 1.1 WHERE category = ?",
undef, "books"
);
print "更新 $rows 行\n";
# 删除
$dbh->do("DELETE FROM products WHERE id = ?", undef, 99);
# 受影响行数
my $rows = $sth->rows;
6. 事务处理
# 关闭自动提交
$dbh->{AutoCommit} = 0;
eval {
$dbh->do("UPDATE account SET balance = balance - 100 WHERE id = 1");
$dbh->do("UPDATE account SET balance = balance + 100 WHERE id = 2");
$dbh->commit; # 提交
};
if ($@) {
$dbh->rollback; # 回滚
warn "事务失败: $@";
}
$dbh->{AutoCommit} = 1;
7. 错误处理与关闭
# RaiseError 自动 die(推荐)
# PrintError 打印警告(默认)
# 手动捕获
eval {
$dbh->do("SELECT * FROM no_such_table");
};
if ($@) {
print "查询错误: $@\n";
}
# 断开
$dbh->disconnect;
# 完整示例
sub get_user {
my ($id) = @_;
my $dbh = connect_db();
my $row = $dbh->selectrow_hashref(
"SELECT * FROM users WHERE id = ?", undef, $id
);
$dbh->disconnect;
return $row;
}
Perl CGI编程
Perl CGI 编程
CGI 是 Perl 曾经的 Web 开发主力,了解 CGI.pm 有助于维护老系统。
1. 基本 CGI 程序
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
my $cgi = CGI->new;
# 输出 HTTP 头
print $cgi->header("text/html; charset=UTF-8");
# 输出内容
print "";
print "你好, Perl CGI!
";
# 读取参数
my $name = $cgi->param("name") || "访客";
print "你好, $name
";
print "";
2. 表单处理
#!/usr/bin/perl
use CGI;
my $cgi = CGI->new;
print $cgi->header("text/html; charset=UTF-8");
# GET/POST 参数
my $username = $cgi->param("username");
my $password = $cgi->param("password");
# 多值参数(复选框)
my @hobbies = $cgi->param("hobby");
# 判断请求方法
if ($cgi->request_method eq "POST") {
print "收到: $username
";
}
# 输出表单
print $cgi->start_form(
-method => "post",
-action => $cgi->url
);
print $cgi->textfield(-name => "username", -size => 20);
print $cgi->password_field(-name => "password");
print $cgi->checkbox_group(
-name => "hobby",
-values => ["编程", "阅读", "运动"],
);
print $cgi->submit(-value => "提交");
print $cgi->end_form;
3. Cookie 与 Session
use CGI;
use CGI::Session;
my $cgi = CGI->new;
# 读取 Cookie
my $cookie = $cgi->cookie("user");
# 设置 Cookie
my $new_cookie = $cgi->cookie(
-name => "user",
-value => "张三",
-expires => "+1d",
);
print $cgi->header(-cookie => $new_cookie);
# Session(需 CGI::Session)
my $session = CGI::Session->new(undef, $cgi, {
Directory => "/tmp/sessions",
});
$session->param("username", "admin");
my $sid = $session->id;
4. 输出 JSON(API)
#!/usr/bin/perl
use CGI;
use JSON::PP;
my $cgi = CGI->new;
my $action = $cgi->param("action");
my %result = (
status => "ok",
data => [1, 2, 3],
);
print $cgi->header("application/json; charset=UTF-8");
print encode_json(\%result);
5. 文件上传
use CGI;
my $cgi = CGI->new;
print $cgi->header("text/html; charset=UTF-8");
# 上传表单
print $cgi->start_multipart_form;
print $cgi->filefield(-name => "upload", -size => 30);
print $cgi->submit("上传");
print $cgi->end_form;
# 处理上传
if ($cgi->param("upload")) {
my $fh = $cgi->upload("upload");
my $filename = $cgi->param("upload");
open my $out, ">", "uploads/$filename" or die $!;
while (my $bytes = read($fh, my $buf, 8192)) {
print $out $buf;
}
close $out;
print "上传成功: $filename
";
}
6. 安全与现代化替代
安全要点:
1. 参数校验与转义(防 XSS)
2. SQL 预处理(防注入)
3. 文件上传限制与改名
4. CGI 脚本权限最小化
现代替代:
- PSGI/Plack + Plack::Request(现代 Perl Web)
- Mojolicious(现代 Perl Web 框架)
- 或直接换 Python/Go/PHP
CGI.pm 已从 Perl 核心移除(5.22+),需 cpanm CGI 安装
Perl 包和模块
Perl 包和模块
包(Package)是命名空间,模块(Module)是封装代码的文件,CPAN 提供海量模块。
1. 包与命名空间
# 包: 隔离变量的命名空间
package MyMath;
sub add { my ($a, $b) = @_; $a + $b }
our $VERSION = "1.0";
package main; # 回到主命名空间
print MyMath::add(1, 2), "\n"; # 3
print $MyMath::VERSION, "\n"; # 1.0
2. 创建模块
# lib/MyMath.pm
package MyMath;
use strict;
use warnings;
use Exporter "import"; # 导出机制
our @EXPORT_OK = qw(add multiply); # 可导出的函数
our @EXPORT = qw(add); # 默认导出
sub add {
my ($a, $b) = @_;
return $a + $b;
}
sub multiply {
my ($a, $b) = @_;
return $a * $b;
}
1; # 必须返回真值
# 使用
use lib "lib";
use MyMath; # 默认导出 add
use MyMath qw(multiply); # 指定导出
print add(1, 2), "\n"; # 3
print multiply(3, 4), "\n"; # 12
3. use 与 require
use MyModule; # 编译期加载 + import
use MyModule qw(fn); # 只导入 fn
require MyModule; # 运行时加载(不 import)
require "MyModule.pm"; # 按路径
# use lib 指定搜索路径
use lib "/path/to/modules";
use lib "lib";
# 搜索路径
print "@INC\n";
4. CPAN 常用模块
数据: JSON::PP、Storable、YAML
文本: Text::CSV、Text::Diff
网络: LWP::UserAgent、HTTP::Tiny
数据库: DBI、DBD::mysql
日期: DateTime、Time::Piece
Web: Mojolicious、Plack
系统: File::Find、File::Copy
测试: Test::More(核心)
日志: Log::Log4perl
# 查找模块
cpan -i Module::Name
cpanm Module::Name
# 查看文档
perldoc Module::Name
5. 编写自己的模块(实践)
# lib/Config/Reader.pm
package Config::Reader;
use strict;
use warnings;
sub new {
my ($class, $file) = @_;
my $self = { file => $file, data => {} };
bless $self, $class;
$self->_load;
return $self;
}
sub get {
my ($self, $key) = @_;
return $self->{data}{$key};
}
sub _load {
my ($self) = @_;
open my $fh, "<", $self->{file} or die $!;
while (my $line = <$fh>) {
chomp $line;
next if $line =~ /^#/ || $line !~ /=/;
my ($k, $v) = split /=/, $line, 2;
$self->{data}{$k} = $v;
}
close $fh;
}
1;
# 使用
use lib "lib";
use Config::Reader;
my $conf = Config::Reader->new("app.conf");
print $conf->get("host"), "\n";
6. 模块最佳实践
1. 包名与文件名对应(My::Module → My/Module.pm)
2. 文件结尾必须 1;
3. use strict; use warnings; 开头
4. 导出函数用 @EXPORT_OK(避免命名冲突)
5. 写 POD 文档
6. 用 Test::More 写单元测试
7. 版本号 our $VERSION = "1.00";
Perl 进程管理
Perl 进程管理
Perl 通过 system、exec、fork、IPC 等机制管理进程。
1. system(执行命令)
# system: 执行并等待
system("ls -la");
system("ls", "-la"); # 数组方式(避免 shell 注入)
# 检查结果
my $status = system("grep foo file.txt");
if ($status == 0) {
print "成功\n";
} else {
print "失败, 退出码: $?\n";
}
2. exec 与反引号
# exec: 替换当前进程
# exec("python3", "script.py");
# 反引号: 捕获输出
my $output = `ls -la`;
print $output;
# qx 相同
my $date = qx(date);
chomp $date;
print "$date\n";
# 捕获 stderr
my $both = `cmd 2>&1`;
3. fork(创建子进程)
my $pid = fork();
if ($pid == 0) {
# 子进程
print "子进程 PID: $$\n";
exit 0;
} elsif ($pid > 0) {
# 父进程
print "父进程 PID: $$, 子进程: $pid\n";
waitpid($pid, 0); # 等待子进程
} else {
die "fork 失败: $!";
}
# 并行任务
my @pids;
for my $task (1..3) {
my $pid = fork();
if ($pid == 0) {
# 子进程做任务
print "任务 $task (PID $$)\n";
sleep 1;
exit 0;
}
push @pids, $pid;
}
# 父进程等待所有
waitpid($_, 0) for @pids;
print "全部完成\n";
4. 信号处理
# 信号处理
$SIG{INT} = sub {
print "收到中断信号,清理退出\n";
exit 0;
};
$SIG{TERM} = "DEFAULT"; # 恢复默认
# 发送信号
kill "TERM", $pid;
kill 9, $pid; # SIGKILL
# 定时器
$SIG{ALRM} = sub { die "超时\n" };
alarm 5; # 5 秒后触发 ALRM
# ... 长时间操作 ...
5. IPC 通信
# 管道
open my $pipe, "|-", "grep error" or die $!;
print $pipe "error line\n";
print $pipe "ok line\n";
close $pipe;
# 读取子进程输出
open my $fh, "-|", "df -h" or die $!;
while (my $line = <$fh>) {
print $line;
}
close $fh;
# IPC::Open3 双向通信
use IPC::Open3;
my $pid = open3(my $in, my $out, my $err, "python3 -");
print $in "print('hi')\n";
close $in;
my $result = <$out>;
print "Python 输出: $result";
6. 并行处理最佳实践
# Parallel::ForkManager 简化并行
cpanm Parallel::ForkManager
use Parallel::ForkManager;
my $pm = Parallel::ForkManager->new(4); # 4 并行
for my $file (@files) {
$pm->start and next; # fork
process_file($file); # 子进程任务
$pm->finish; # 结束
}
$pm->wait_all_children;
print "全部完成\n";
# 或使用线程
use threads;
my @threads = map {
threads->create(\&work, $_)
} (1..3);
$_->join for @threads;
Perl POD 文档
Perl POD 文档
POD(Plain Old Documentation)是 Perl 内置的文档格式,与代码共存于同一文件。
1. POD 基础语法
#!/usr/bin/perl
use strict;
use warnings;
=head1 NAME
MyApp - 示例程序
=head1 SYNOPSIS
myapp [options] file
=head1 DESCRIPTION
这是一个示例程序,用于演示 POD 文档的写法。
=head2 功能
- 处理文本
- 输出报告
=head1 OPTIONS
=over 4
=item B<-f> --file
输入文件
=item B<-o> --output
输出文件
=back
=head1 AUTHOR
张三
=cut
print "Hello\n";
# 注意: =head1 到 =cut 之间是 POD
# 代码在 =cut 之后继续
2. 查看文档
perldoc myapp.pl # 查看 POD 文档
perldoc -f print # 内置函数文档
perldoc Module::Name # 模块文档
perldoc perlintro # Perl 简介
# 转其他格式
pod2html myapp.pl > doc.html
pod2text myapp.pl # 纯文本
pod2man myapp.pl # man 格式
3. 常用 POD 命令
=head1 一级标题
=head2 二级标题
=head3 三级标题
=over 4 开始列表
=item 列表项
=back 结束列表
=cut 结束 POD
=pod 开始 POD
=begin 特殊块
=for 段落格式
B 加粗
I 斜体
C 代码
L 链接
E 转义 <
4. 模块 POD 模板
package MyModule;
use strict;
use warnings;
=head1 NAME
MyModule - 简短描述
=head1 VERSION
Version 1.00
=head1 SYNOPSIS
use MyModule;
my $obj = MyModule->new;
$obj->do_something;
=head1 DESCRIPTION
详细描述模块的功能与用法。
=head1 METHODS
=head2 new
构造函数。
=head2 do_something
执行某个操作。
=head1 AUTHOR
张三, Ezhangsan@example.comE
=head1 LICENSE
This is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
sub new { ... }
sub do_something { ... }
1;
5. 文档测试与检查
# 语法检查
perl -c myapp.pl
perl -c MyModule.pm
# POD 检查
podchecker MyModule.pm
# 文档中代码示例测试(Test::Pod)
cpanm Test::Pod
# t/pod.t
use Test::More;
eval "use Test::Pod 1.00";
plan skip_all => "Test::Pod 1.00 required"
if $@;
all_pod_files_ok();
6. 最佳实践
1. 每个模块写 NAME/SYNOPSIS/DESCRIPTION
2. 公开方法都要有 POD 文档
3. 文档与代码同步更新
4. 用 perldoc 验证渲染效果
5. 复杂模块配 EXAMPLES 章节
6. 文档中示例可测试(保障正确)