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. 文档中示例可测试(保障正确)
第 1 / 29 节