cv.cm

编码 · 编程语言

Perl 入门教程

阅读约 10 分钟

简短回答

Perl 是 Larry Wall 于 1987 年创建的动态脚本语言,以文本处理和强大的内置正则表达式闻名。大多数 Unix 系统都预装了它,常用于系统管理、日志处理和生物信息学。本页涵盖运行 Perl、标量、数组与哈希、流程控制、子程序、引用、正则表达式,以及用 die 和 eval 处理错误。

打开正则测试

Perl 是什么,用来做什么

Perl 是 Larry Wall 于 1987 年创建的高级动态类型语言,最初为文本处理和报表生成而设计,后来成为 Unix 系统管理和早期 Web 开发的「胶水语言」。Perl 的正则表达式影响深远,以至于许多其他语言都采用了「兼容 Perl」的正则语法。

如今 Perl 用于系统管理脚本、日志和文本处理、生物信息学、遗留 Web 应用以及单行命令行过滤器。大多数 Linux 和 Unix 系统都预装了它。CPAN(Comprehensive Perl Archive Network,Perl 综合存档网络)托管着数量庞大的可复用模块。本教程讲解 Perl 5;曾被称为 Perl 6 的语言现在是名为 Raku 的独立语言。

运行 Hello, world

用 perl -v 检查 Perl 是否已安装。在 Windows 上,Strawberry Perl 是常见的发行版。每个脚本都以 use strict; 和 use warnings; 开头:它们能发现拼写错误和不安全的代码。

#!/usr/bin/env perl
use strict;
use warnings;

print "Hello, world!\n";
perl hello.pl
# Hello, world!
perl -e 'print "Hello from a one-liner\n"'

语句以分号结尾。注释以 # 开头。双引号字符串会插值变量和 \n 等转义序列;单引号字符串则是字面文本。

变量:标量、数组与哈希

Perl 变量以一个标志符(sigil)开头,用来表示值的种类。每个变量都用 my 声明,使其局限在所在的代码块内。

use strict;
use warnings;

my $name  = "Perl";             # scalar: one value
my $year  = 1987;
my @langs = ("Perl", "C", "sh"); # array: ordered list
my %port  = (http => 80, https => 443);  # hash: key-value pairs

print "$name since $year\n";    # Perl since 1987
print "First: $langs[0]\n";     # First: Perl (element is a scalar, so $)
print "Count: ", scalar(@langs), "\n";   # Count: 3
print "HTTPS: $port{https}\n";  # HTTPS: 443
print "Sum: ", "10" + 5, "\n";  # Sum: 15 (strings convert to numbers)
print "Join: " . "a" . "b" x 3, "\n";    # Join: abbb
符号种类示例
$标量:数字、字符串或引用my $n = 42;
@数组:标量的有序列表my @list = (1, 2);
%哈希:无序的键值对my %h = (a => 1);
&子程序(很少这样写)&name

流程控制

Perl 有 if、elsif、else、unless、while、until、for 和 foreach。简短的语句后面可以跟一个条件,读起来像英语。next 跳到下一次迭代,last 退出循环。

use strict;
use warnings;

my $score = 72;
if ($score >= 90) {
    print "A\n";
} elsif ($score >= 70) {
    print "B\n";                 # B
} else {
    print "C\n";
}
print "passed\n" if $score >= 50;          # statement modifier
print "not perfect\n" unless $score == 100;

for my $i (1 .. 3) {
    print "i = $i\n";
}

my @names = ("ann", "bo");
foreach my $n (@names) {
    next if $n eq "bo";          # skip to the next item
    print ucfirst($n), "\n";     # Ann
}

my $k = 3;
while ($k > 0) { $k--; }

假值有 0、"0"、空字符串 ""、空列表和 undef。其他一切都是真。

子程序

用 sub 定义函数。参数通过特殊数组 @_ 传入,通常的第一行是把它们复制到具名变量中。子程序可以返回标量或列表。

use strict;
use warnings;

sub add {
    my ($x, $y) = @_;            # arguments arrive in @_
    return $x + $y;
}

sub greet {
    my ($name, $greeting) = @_;
    $greeting //= "Hello";       # default if undefined
    return "$greeting, $name!";
}

sub stats {
    my @nums = @_;
    my $sum = 0;
    $sum += $_ for @nums;
    return ($sum, $sum / @nums); # return a list
}

print add(2, 3), "\n";           # 5
print greet("Ann"), "\n";        # Hello, Ann!
my ($total, $avg) = stats(4, 8, 6);
print "$total $avg\n";           # 18 6

现代 Perl 也支持签名,如 sub add($x, $y) { ... },用 use v5.36; 或更高版本启用,同时也会开启 strict 和 warnings。

数组、哈希与引用

数组和哈希自带强大的内置函数,如 push、pop、shift、sort、map、grep、keys 和 exists。嵌套数据使用引用:[ ... ] 创建数组引用,{ ... } 创建哈希引用,用 -> 解引用。

use strict;
use warnings;

my @nums = (5, 3, 8, 1);
push @nums, 9;                           # add to the end
my $last = pop @nums;                    # remove from the end: 9
my @sorted  = sort { $a <=> $b } @nums;  # numeric sort
my @doubled = map  { $_ * 2 } @sorted;
my @big     = grep { $_ > 4 } @sorted;
print "@sorted | @doubled | @big\n";     # 1 3 5 8 | 2 6 10 16 | 5 8

my %age = (ann => 31, bo => 25);
$age{cy} = 40;
delete $age{bo};
for my $who (sort keys %age) {
    print "$who is $age{$who}\n";        # ann is 31 / cy is 40
}
print "has ann\n" if exists $age{ann};

# references build nested data structures
my $user = { name => "Ann", langs => ["Perl", "SQL"] };
print "$user->{name} knows $user->{langs}[1]\n";  # Ann knows SQL
print scalar @{ $user->{langs} }, "\n";           # 2

在 sort 代码块内,$a 和 $b 是正在比较的两个元素。<=> 按数值比较,cmp 按字符串比较;单纯的 sort @list 按字符串排序,所以 10 排在 9 前面。

正则表达式

正则表达式内置于 Perl 的语法中,这是它的标志性特性。=~ 应用匹配 m/.../ 或替换 s/.../.../。捕获的分组可通过 $1、$2 等访问,g(全局)和 i(不区分大小写)等修饰符写在结尾的斜杠之后。

use strict;
use warnings;

my $line = "2026-10-31 ERROR disk full on /dev/sda1";

if ($line =~ /^(\d{4})-(\d\d)-(\d\d)\s+(\w+)/) {
    print "year=$1 level=$4\n";          # year=2026 level=ERROR
}

(my $masked = $line) =~ s/\d/#/g;        # copy, then replace digits
print "$masked\n";                       # ####-##-## ERROR disk full on /dev/sda#

my @words = split /\s+/, $line;
print scalar(@words), " words\n";        # 6 words

my %h = ("a=1, b=2, c=3" =~ /(\w)=(\d)/g); # pairs into a hash
print join(",", map { "$_$h{$_}" } sort keys %h), "\n";  # a1,b2,c3

用 qr/.../ 把编译好的模式存入变量,用 x 修饰符可以把长模式写成多行并加注释。

错误处理

die 抛出异常,warn 向标准错误输出警告。eval { ... } 捕获异常,并把错误消息放入 $@。open 等系统调用失败时返回假值,并把操作系统错误设置到 $!,所以要用 or die 检查它们。

use strict;
use warnings;

sub divide {
    my ($x, $y) = @_;
    die "division by zero\n" if $y == 0;
    return $x / $y;
}

my $result = eval { divide(1, 0) };
if ($@) {
    print "Caught: $@";                  # Caught: division by zero
}

open(my $fh, "<", "missing.txt")
    or print "Cannot open: $!\n";        # Cannot open: No such file or directory

以 \n 结尾的 die 消息会原样输出;没有换行符时,Perl 会附加文件名和行号。自 Perl 5.34 起还有实验性的 try/catch 特性,Try::Tiny 等模块也被广泛使用。

一个完整的小程序:日志级别计数器

这个脚本从命令行指定的文件或标准输入读取日志,按级别统计行数并输出排序后的汇总。它演示了菱形运算符 <>、正则表达式、哈希和 printf。

#!/usr/bin/env perl
# logstat.pl: count log levels in a file or standard input
use strict;
use warnings;

my %count;
my $lines = 0;

while (my $line = <>) {                  # reads files named on the command line, or STDIN
    chomp $line;
    $lines++;
    next unless $line =~ /\b(INFO|WARN|ERROR)\b/;
    $count{$1}++;
}

printf "%d lines read\n", $lines;
for my $level (sort { $count{$b} <=> $count{$a} || $a cmp $b } keys %count) {
    printf "%-5s %3d\n", $level, $count{$level};
}
perl logstat.pl app.log
# 120 lines read
# INFO   97
# WARN   18
# ERROR   5

接下来学什么

  • perldoc.perl.org 上的 perlintro 及其他教程,也可以离线用 perldoc perlintro 查看。
  • 《Learning Perl》和《Modern Perl》,两本著名的入门书。
  • MetaCPAN:CPAN 模块的搜索界面。
  • perlre 和 perlretut:完整的正则表达式参考。

常见问题

Perl 现在还有人用吗?

有。大多数类 Unix 系统都预装了 Perl,它在系统管理、文本处理、生物信息学和长期运行的业务系统中依然常见,不过用 Perl 启动的新 Web 项目已经变少。

Perl 和 Raku 有什么区别?

Raku 最初是 Perl 6,后来成为设计不同的独立语言。Perl 5 继续独立发展,Perl 这个名字指的就是它。

为什么 Perl 变量以 $、@ 和 % 开头?

标志符表示你得到的是哪种值:$ 表示单个标量,@ 表示列表,% 表示哈希。这就是 @items 的单个元素要写成 $items[0] 的原因。

Perl 中的 chomp 有什么作用?

chomp 去掉字符串末尾的换行符,从文件或键盘读取一行后通常需要这样做。

Perl 适合处理正则表达式吗?

适合。正则表达式是 Perl 核心语法的一部分,许多其他工具和语言使用的正则库 PCRE,其名称就来自 Perl 的正则风格。

全部编码教程