Skip to content

Perl 速查表

用于文本处理的高级解释型编程语言。

01

入门

基础与 Hello World

Perl 使用 sigil 表示变量类型:$ 表示标量,@ 表示数组,% 表示哈希。始终使用 strict 和 warnings 以获得更干净的代码。

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

print "Hello, World!\n";

# variables: scalar ($), array (@), hash (%)
my $name = "Alice";
my @nums = (1, 2, 3);
my %ages = (Alice => 30, Bob => 25);

print "Name: $name\n";

注释

单行注释以 # 开头。多行注释使用由 =pod 和 =cut 界定的 POD 块。POD 也用于文档编写。

perl
#!/usr/bin/perl
# This is a single-line comment

print "Hello\n";  # inline comment

=pod
This is a multi-line
comment using POD (Plain Old Documentation)
=cut

print "After POD\n";

标量简介

标量保存单个值:字符串、数字或引用。Perl 会在字符串和数字之间自动转换。用 . 连接字符串,用 x 重复字符串。

perl
my $str = "Hello";
my $num = 42;
my $float = 3.14;
my $bool = 1;  # true (0, "", "0", undef are false)

# String repetition
my $line = "-" x 20;

# String concatenation
my $greeting = "Hello, " . $str;

print "$greeting\n";  # Hello, Hello

字符串插值

双引号字符串会插值 $ 和 @ 变量;单引号是字面量。当变量名后紧跟会扩展标识符的字符时,用花括号来界定变量名,例如表示复数。

perl
my $name = "Alice";
my $age = 30;

print "Name: $name\n";   # interpolates
print 'Name: $name\n';   # literal (no interpolation)

# Disambiguate variable name with braces
my $fruit = "apple";
print "I have many ${fruit}s.\n";  # I have many apples.

# Array interpolation (joins with $")
my @arr = (1, 2, 3);
print "Array: @arr\n";  # Array: 1 2 3

use strict 与 warnings

strict 强制词法作用域(my/our)并捕获拼写错误。warnings 启用有用的诊断信息。现代代码可用 'use v5.36'(或更高)一并启用这些与新特性。

perl
use strict;
use warnings;

# Without strict, this would create a global:
# $foo = 42;   # ERROR under strict
my $foo = 42;  # OK with my

# Warnings catch common mistakes
my $undef_var;
# print $undef_var;  # warns: uninitialized value

# Modern bundle (v5.36+ enables strict, warnings, features)
use v5.36;

# Disable specific warnings locally
no warnings 'uninitialized';

say 与特性

say 类似 print 但自动追加换行。用 'use feature' 或版本束如 'use v5.36' 启用特性。签名特性提供命名参数,无需再解包 @_。

perl
use v5.10;
use feature 'say';

say "Hello!";  # adds newline automatically
say "No need for \n";

# Equivalent to:
print "Hello!\n";

# Signatures (v5.20+, not default)
use feature 'signatures';
no warnings 'experimental::signatures';
sub greet ($name) {
    say "Hi, $name";
}
02

标量

数字与运算符

Perl 拥有完整的算术运算符。除法总是返回浮点数,除非用 int()。** 是幂运算。++ 和 -- 对数字和字符串都有效(自增 'a' 得到 'b')。

perl
my $sum  = 5 + 3;        # 8
my $diff = 10 - 4;       # 6
my $prod = 6 * 7;        # 42
my $div  = 10 / 3;       # 3.3333...
my $int  = int(10 / 3);  # 3
my $mod  = 10 % 3;       # 1
my $pow  = 2 ** 10;      # 1024

# Augmented assignment
my $x = 5;
$x += 3;   # $x = 8
$x **= 2;  # $x = 64
$x++;      # $x = 65
$x--;      # $x = 64

字符串运算符

用 . 连接(不是 +),用 x 重复。字符串比较用 eq/ne/lt/gt;数字比较用 ==/!=/</>。混用可能产生意外结果。

perl
my $a = "Hello";
my $b = "World";

# Concatenation
my $combined = $a . ", " . $b;  # Hello, World

# Repetition
my $line = "=" x 30;

# Augmented concatenation
my $str = "abc";
$str .= "def";  # abcdef

# Numeric vs string comparison
print "match" if "abc" eq "abc";  # string equal
print "match" if 123 == 123;      # numeric equal

undef 与 defined

undef 表示未定义(或缺失)的值。用 defined() 检测它。//(defined-or)运算符仅当左边为 undef 时返回右边,而 || 还会在 0 或 '' 时触发。

perl
my $x;               # undef
print "undef\n" if !defined $x;

$x = undef;          # explicitly undef
undef $x;            # another way

# Defined-or operator
my $val = $x // "default";

# Warning if used as string:
# print $x;  # Use of uninitialized value

if (defined $val) {
    print "Defined: $val\n";
}

引号 (q/qq/qw)

q// 是单引号,qq// 是双引号,qw// 按空白拆分为列表。可使用任意匹配定界符((), {}, [], //, !!),便于避免字符串内部的引号转义。

perl
my $name = "Alice";

# q() = single quote (no interpolation)
my $s1 = q(Hello $name);    # Hello $name

# qq() = double quote (interpolation)
my $s2 = qq(Hello $name);   # Hello Alice

# qw() = quoted words (produces a list)
my @words = qw(apple banana cherry);
# equivalent to ("apple", "banana", "cherry")

# Custom delimiters
my $s3 = q{don't worry};
my $s4 = q!bang!;

Here 文档

Heredoc 用于编写多行字符串。<<'END' 是字面量(无插值),<<"END" 会插值。~ 变体(v5.26+)去除前导空白,使 heredoc 可与周围代码一起缩进。

perl
my $name = "Alice";

# Single-quote style (no interpolation)
my $text = <<'END';
Hello $name,
This is literal.
END

# Double-quote style (interpolation)
my $msg = <<"EOF";
Hello $name,
Interpolated here.
EOF

print $msg;

# Indented heredoc (v5.26+)
my $code = <<~EOT;
    line 1
    line 2
    EOT

布尔真值

只有 undef、0、'' 和 '0' 为假;连 '00' 和 '0.0' 都是真!用 || 设真值默认,用 // 设已定义默认。引用(即使是空数组)始终为真。

perl
# False values: undef, 0, "", "0"
# Everything else is true

print "true\n" if 1;        # true
print "true\n" if "hello";  # true
print "true\n" if "0";      # NOT printed (false!)
print "true\n" if "";       # NOT printed (false)
print "true\n" if [];       # true (reference is true)

# Logical operators
my $x = 0 || "default";   # default (|| tests truthiness)
my $y = 0 // "default";   # default (// tests definedness)
my $z = "a" && "b";       # b (returns last evaluated)
03

数组

数组基础

数组用 @ sigil 表示整个数组,用 $ 访问单个元素(因为元素是标量)。$#arr 给出最后索引。标量上下文中数组返回其长度。

perl
my @nums  = (1, 2, 3, 4, 5);
my @strs  = qw(a b c);
my @mixed = (1, "two", 3.14);

# Access by index (0-based)
print $nums[0];    # 1
print $nums[-1];   # 5 (last element)

# Modify an element
$nums[0] = 100;

# Range operator
my @range = (1 .. 10);  # 1 to 10

# Length in scalar context
my $count = scalar @nums;  # 5
my $count2 = @nums;        # 5

push/pop/unshift/shift

push/pop 在末尾增删(快);unshift/shift 在开头增删(较慢,需重索引)。splice 是在任意位置增删替换的通用工具。

perl
my @stack = (1, 2, 3);

# push/pop operate on the END
push @stack, 4, 5;        # (1, 2, 3, 4, 5)
my $last = pop @stack;    # 5

# unshift/shift operate on the START
unshift @stack, 0;        # (0, 1, 2, 3, 4)
my $first = shift @stack; # 0

# @stack is now (1, 2, 3, 4)
print "@stack\n";

# splice is the general-purpose tool
splice(@stack, 1, 0, 'x');  # insert at index 1

数组切片

数组切片 @arr[索引] 返回元素列表(注意 @ sigil,得到的是列表而非标量)。用范围 0..2 取连续切片,用 $#arr 取最后索引。

perl
my @nums = (10, 20, 30, 40, 50);

# Array slice (returns a list)
my @slice = @nums[1, 3];      # (20, 40)
my @range = @nums[0 .. 2];    # (10, 20, 30)

# Modify multiple elements via slice
@nums[1, 3] = (200, 400);

# Last index
my $last_idx = $#nums;  # 4

# Negative indices count from the end
print $nums[-1];  # 50
print $nums[-2];  # 400

splice

splice 是通用数组修改工具:删除、插入或替换任意连续片段。LENGTH 为 0 表示插入不删除;省略 LENGTH 表示删除到末尾。

perl
my @arr = (1, 2, 3, 4, 5);

# splice ARRAY, OFFSET, LENGTH, LIST
my @removed = splice(@arr, 1, 2);  # removes (2, 3)
# @arr is now (1, 4, 5)

# Insert without removing (LENGTH = 0)
splice(@arr, 1, 0, 'a', 'b');  # (1, 'a', 'b', 4, 5)

# Replace elements
splice(@arr, 0, 2, 'x');  # replace first 2 with 'x'

# Remove everything from offset to end
my @rest = splice(@arr, 2);

排序

sort 接受使用特殊变量 $a 和 $b 的比较块。用 <=> 做数字比较,用 cmp 做字符串比较。Schwartzian 变换缓存排序键以提升效率。

perl
my @nums = (3, 1, 4, 1, 5, 9);

# Default sort is lexicographic (string)
my @s1 = sort @nums;

# Numeric ascending
my @s2 = sort { $a <=> $b } @nums;

# Numeric descending
my @desc = sort { $b <=> $a } @nums;

# String sort
my @words = qw(banana apple Cherry);
my @alpha = sort { lc($a) cmp lc($b) } @words;

# Schwartzian transform (sort by computed key)
my @sorted = map  { $_->[0] }
             sort { $a->[1] <=> $b->[1] }
             map  { [$_, length($_)] } @words;

数组长度与标量上下文

数组行为取决于上下文:标量上下文返回长度,列表/字符串上下文返回用 $"(默认空格)连接的元素。需要时用 scalar() 强制标量上下文。

perl
my @arr = (1, 2, 3, 4, 5);

# Scalar context returns length
my $len = @arr;        # 5
print "Length: $len\n";

# String context joins with $"
print "Array: @arr\n";  # 1 2 3 4 5

# Change separator
local $" = ", ";
print "@arr\n";        # 1, 2, 3, 4, 5

# Last index
print "Last index: $#arr\n";  # 4

# Force scalar context
print scalar(@arr);    # 5
04

哈希

哈希基础

哈希(关联数组)用 % sigil。胖箭头 => 会引用其左侧,优于普通逗号。用 $hash{key} 访问单个值(标量上下文,单个值)。

perl
my %ages = (Alice => 30, Bob => 25, Carol => 35);

# Fat comma => auto-quotes the left side
my %h2 = (name => 'Alice', age => 30);

# Same as list of pairs:
my %h3 = ('name', 'Alice', 'age', 30);

# Access / modify a value
print $ages{Alice};   # 30
$ages{Dave} = 40;     # add
$ages{Alice} = 31;    # modify

# Delete a key
delete $ages{Bob};

遍历 (keys/values/each)

keys/values 返回列表(无序)。each 一次迭代一对键值,对大哈希节省内存,但迭代时不要修改哈希。用 sort keys 获得确定性顺序。

perl
my %h = (a => 1, b => 2, c => 3);

# Iterate keys
for my $k (keys %h) {
    print "$k => $h{$k}\n";
}

# Get all values
my @vals = values %h;

# each() returns key/value pairs iteratively
while (my ($k, $v) = each %h) {
    print "$k = $v\n";
}

# Sorted keys
for my $k (sort keys %h) { ... }

exists 与 delete

exists 测试键是否存在于哈希中(即使其值为 undef)。defined 测试值本身。delete 删除键并返回其值。无键的哈希在布尔上下文中为假。

perl
my %h = (name => 'Alice', age => 30);

# exists checks if a key is present
if (exists $h{name}) {
    print "Name exists\n";
}

# defined checks if the value is defined
if (defined $h{age}) { ... }

# delete removes a key, returns its value
my $removed = delete $h{age};

# Delete multiple keys (slice)
delete @h{qw(name age)};

# Empty a hash
%h = ();

哈希切片

@h{keys} 返回这些键对应的值(注意 @ sigil)。自 v5.20 起,%h{keys} 返回键/值对。切片便于一次获取或设置多个值。

perl
my %h = (a => 1, b => 2, c => 3);

# Hash slice (returns list of values)
my @vals = @h{qw(a b)};   # (1, 2)

# Modify slice
@h{qw(a b)} = (10, 20);

# Key/value slice (v5.20+)
my @pairs = %h{qw(a b)};  # (a, 10, b, 20)

# Index/value slice
my @iv = %h{qw(a b)};

# Check multiple keys
my @present = grep { exists $h{$_} } qw(a x b);

多维哈希

Perl 哈希保存标量值,因此嵌套结构使用哈希/数组引用。自动激活(autovivification)在给深层键赋值时自动创建中间引用,方便但可能隐藏 bug。

perl
my %people = (
    alice => { age => 30, city => 'NYC' },
    bob   => { age => 25, city => 'LA' },
);

# Access nested values
print $people{alice}{age};   # 30
print $people{bob}{city};    # LA

# Add new nested entry
$people{carol}{age}  = 35;
$people{carol}{city} = 'SF';

# Auto-vivification creates intermediate refs
$people{dave}{hobbies}[0] = 'reading';
# %people{dave} created automatically

常见哈希操作

scalar keys %h 给出键数。reverse %h 交换键/值(值不唯一时有损)。用 (%a, %b) 合并会先展平再重建。List::Util 提供值的 min/max。

perl
my %h = (a => 1, b => 2, c => 3);

# Number of keys
my $count = scalar keys %h;

# Reverse a hash (values become keys)
my %rev = reverse %h;  # (1 => a, 2 => b, 3 => c)

# Merge hashes
my %merged = (%h, d => 4);

# Check if empty
print "empty\n" if !keys %h;

# Reset each() iterator
keys %h;  # resets iterator (v5.18+)

# Get min/max value
use List::Util qw(min max);
my ($min, $max) = (min(values %h), max(values %h));
05

控制结构

if/elsif/else

Perl 使用 elsif(没有第二个 e)。语句修饰形式(statement if EXPR)对简单情况读起来很自然。所有块都需要花括号,即使只有一条语句。

perl
my $score = 85;

if ($score >= 90) {
    print "A\n";
} elsif ($score >= 80) {
    print "B\n";
} elsif ($score >= 70) {
    print "C\n";
} else {
    print "F\n";
}

# Statement modifier form
print "Pass\n" if $score >= 60;

# Note: it is 'elsif', NOT 'elseif'

unless

unless 是 if 的否定。作为语句修饰(do X unless Y)读起来很顺,但 unless/else 可能令人困惑。复杂条件建议用 if 加 !。

perl
my $logged_in = 0;

unless ($logged_in) {
    print "Please log in\n";
}
# Equivalent: if (!$logged_in) { ... }

# Statement modifier
print "Error\n" unless $ok;

# unless/else (often avoid for readability)
unless ($ok) {
    print "fail\n";
} else {
    print "pass\n";
}

while 与 until

while 在条件为真时重复;until 重复直到条件为真。do { } while/until 形式保证至少执行一次。用 last 跳出无限循环。

perl
# while loop
my $i = 0;
while ($i < 5) {
    print "$i\n";
    $i++;
}

# until = while NOT
my $done = 0;
until ($done) {
    $done = check_something();
}

# do-while style (runs at least once)
my $n = 0;
do {
    print $n++;
} while ($n < 3);

# Infinite loop
while (1) { last if $done; }

for 与 foreach

for 和 foreach 在 Perl 中是别名。C 风格有三部分;列表形式遍历一个列表。未显式指定循环变量时使用默认的 $_。

perl
# C-style for loop
for (my $i = 0; $i < 5; $i++) {
    print "$i\n";
}

# foreach over a list (for and foreach are interchangeable)
foreach my $item (1, 2, 3) {
    print "$item\n";
}

# Range operator
for my $n (1 .. 10) {
    print "$n ";
}

# Uses default $_ variable
for (1 .. 5) {
    print "$_\n";
}

last/next/redo

last 退出循环(类似 break),next 跳到下一次迭代(类似 continue),redo 重新运行当前迭代而不重新求值条件。标签可控制外层循环。

perl
# last = break out of loop
for my $i (1 .. 10) {
    last if $i == 5;
    print "$i ";
}
# prints: 1 2 3 4

# next = skip to next iteration
for my $i (1 .. 5) {
    next if $i % 2 == 0;
    print "$i ";  # 1 3 5
}

# redo = re-run current iteration without re-checking
my $count = 0;
while (<$fh>) {
    $count++;
    redo if $count < 3;
}

# Labeled loops for nested control
OUTER: for my $i (1..3) {
    for my $j (1..3) {
        last OUTER if $i == 2 && $j == 2;
    }
}

given/when (switch)

given/when 用智能匹配提供 switch/case 语义。它是实验性的,需禁用警告或改用显式 if/elsif 链。匹配可对比值、数组或正则。

perl
use feature 'switch';
no warnings 'experimental::smartmatch';

my $val = 2;

given ($val) {
    when (1)       { print "one\n" }
    when (2)       { print "two\n" }
    when ([3, 4])  { print "three or four\n" }
    when (/^\d+$/) { print "a number\n" }
    default        { print "something else\n" }
}

# given/when is experimental; many prefer if/elsif
if    ($val == 1) { print "one\n" }
elsif ($val == 2) { print "two\n" }
06

正则表达式

匹配 (=~)

=~ 把字符串绑定到正则;!~ 是否定形式。不带 =~ 时正则匹配默认变量 $_。修饰符如 /i(不区分大小写)放在闭合斜杠之后。

perl
my $str = "Hello, World";

# =~ binding operator applies a regex
if ($str =~ /World/) {
    print "Matched\n";
}

# Negated match
if ($str !~ /foo/) {
    print "No foo\n";
}

# Case-insensitive with /i
if ($str =~ /hello/i) {
    print "Found hello\n";
}

# Implicit match against $_
for ("cat", "dog", "bat") {
    print "$_\n" if /at/;  # cat, bat
}

替换 (s///)

s/PATTERN/REPLACEMENT/ 做替换。/g 修饰符替换所有出现;/e 把替换部分作为 Perl 代码求值。tr/// 用于逐字符转换,不是正则。

perl
my $str = "Hello, World";

# Replace first occurrence
$str =~ s/World/Perl/;  # Hello, Perl

# Replace all (g = global)
my $text = "a-b-c";
$text =~ s/-/_/g;  # a_b_c

# Case-insensitive replace
$str =~ s/hello/hi/i;

# Evaluate replacement as code (e)
my $s = "2+3";
$s =~ s/(\d+)\+(\d+)/$1 + $2/e;  # 5

# tr/// transliteration (character mapping)
my $dna = "ACGT";
$dna =~ tr/ACGT/TGCA/;  # TGCA

捕获 ($1, 命名捕获)

括号分组捕获到 $1、$2 等。命名捕获 (?<name>...) 进入 %+。捕获变量持续到下一次成功匹配,因此需要时请先复制它们。

perl
my $str = "2024-01-15";

# Numbered capture groups
if ($str =~ /(\d{4})-(\d{2})-(\d{2})/) {
    print "Year: $1\n";   # 2024
    print "Month: $2\n";  # 01
    print "Day: $3\n";    # 15
}

# Named captures (v5.10+)
if ($str =~ /(?<year>\d{4})-(?<month>\d{2})/) {
    print $+{year};   # 2024
    print $+{month};  # 01
}

# Capture all matches globally
my @nums = ($str =~ /(\d+)/g);  # (2024, 01, 15)

量词与锚点

量词默认贪婪;加 ? 可惰性匹配(*?, +?, ??)。在 /m 下 ^ 和 $ 匹配行边界;\A 和 \z 始终匹配字符串绝对开头/结尾。\b 匹配单词边界。

perl
# Quantifiers
/a/      # exactly one
/a?/     # 0 or 1
/a*/     # 0 or more
/a+/     # 1 or more
/a{3}/   # exactly 3
/a{2,4}/ # 2 to 4
/a{3,}/  # 3 or more

# Greedy vs non-greedy (lazy)
/a.*/    # greedy (matches as much as possible)
/a.*?/   # non-greedy (matches as little as possible)

# Anchors
/^start/   # beginning of string
/end$/    # end of string
/\bword\b/ # word boundary
/\A/       # absolute start of string
/\z/       # absolute end of string

字符类

字符类 [...] 匹配集合中的一个字符。开头加 ^ 取反。简写类(\d \w \s)和 POSIX 类([:alpha:])覆盖常见情况。. 匹配除换行外任意字符。

perl
# Predefined classes
/\d/   # digit            [0-9]
/\D/   # non-digit
/\w/   # word char        [a-zA-Z0-9_]
/\W/   # non-word char
/\s/   # whitespace
/\S/   # non-whitespace
/./    # any char except newline

# Custom classes
/[aeiou]/    # any vowel
/[^0-9]/     # NOT a digit
/[a-z]/i     # case-insensitive range

# POSIX classes
/[[:alpha:]]/   # letters
/[[:digit:]]/   # digits
/[[:space:]]/   # whitespace
/[[:upper:]]/   # uppercase

修饰符

修饰符改变匹配行为:/i 不区分大小写、/g 全局、/m 多行、/s 单行、/x 扩展(允许空白/注释)。qr// 一次编译正则以复用,提升性能。

perl
# Common modifiers
/abc/i   # case-insensitive
/abc/g   # global (all matches)
/abc/m   # multiline (^/$ match line boundaries)
/abc/s   # single-line (. matches newline)
/abc/x   # extended (whitespace and comments)

# Extended mode for readability
my $re = qr{
    ^\d{4}     # year
    -
    \d{2}      # month
    -
    \d{2}      # day
}x;

# qr// pre-compiles a regex
my $pattern = qr/\bword\b/i;
if ($str =~ $pattern) { ... }
07

字符串操作

length 与 substr

length 返回字符数。substr 提取或替换子串;负偏移从末尾计数。4 参数形式(或左值形式)就地修改字符串。

perl
my $s = "Hello, World";

# length
print length($s);  # 12

# substr STRING, OFFSET, LENGTH, REPLACEMENT
print substr($s, 0, 5);   # Hello
print substr($s, 7);      # World
print substr($s, -5);     # World (negative offset)

# 4-argument substr (replace in place)
my $str = "Hello, World";
substr($str, 0, 5) = "Hi";  # Hi, World

# Lvalue form modifies original
substr($s, 0, 1) = "J";

index 与 rindex

index 查找子串首次出现(可指定起始位置);rindex 查找最后一次。未找到均返回 -1。纯子串搜索用它们比正则更快。

perl
my $s = "Hello, World";

# index STRING, SUBSTR, POSITION
my $pos  = index($s, "World");  # 7
my $pos2 = index($s, "o");      # 4 (first)
my $pos3 = index($s, "o", 5);   # 8 (from position 5)
my $not  = index($s, "xyz");    # -1 (not found)

# rindex searches from the end
my $last = rindex($s, "o");     # 8

# Count occurrences of 'l'
my $count = 0;
my $p = 0;
while (($p = index($s, "l", $p)) >= 0) {
    $count++;
    $p++;
}

chomp 与 chop

chomp 只移除输入记录分隔符(通常是 \n),安全;chop 盲目移除最后一个字符。读行时总用 chomp。两者都作用于数组并返回有用值。

perl
my $line = "Hello\n";

# chomp removes the line terminator (safe)
chomp $line;   # $line = "Hello", returns 1

# chomp a whole array at once
my @lines = ("a\n", "b\n", "c\n");
chomp @lines;  # removes \n from all

# chop removes the last character (any char, risky)
my $s = "abc";
chop $s;  # "ab"

# chomp returns the count removed
my $cnt = chomp(my $x = "test\n");  # $cnt = 1

# chop returns the removed character
my $c = chop my $y = "xyz";  # $c = 'z'

split 与 join

split 用正则把字符串拆分为列表;LIMIT 限制片段数。join 用分隔符(普通字符串,非正则)把列表粘合。简单情况下互为逆操作。

perl
# split /PATTERN/, EXPR, LIMIT
my @parts = split /,/, "a,b,c";      # (a, b, c)
my @lim   = split /,/, "a,b,c", 2;   # (a, "b,c")

# Split on whitespace
my @words = split /\s+/, "one two three";

# Split into characters
my @chars = split //, "abc";  # (a, b, c)

# join SEPARATOR, LIST
my $csv = join(",", "a", "b", "c");  # a,b,c
my $str = join(" - ", @parts);

# Empty separator joins without delimiter
my $joined = join("", @chars);  # abc

大小写转换

lc/uc/lcfirst/ucfirst 是函数。\L、\U、\u、\l 是双引号内的字符串大小写修饰符,以 \E 结束。便于格式化插值字符串。

perl
my $s = "Hello World";

print lc($s);       # hello world
print uc($s);       # HELLO WORLD
print lcfirst($s);  # hello World
print ucfirst($s);  # Hello World (already capped)

# In-string case modifiers (\L \U \u \l \E)
my $name = "Alice";
print "\L$name\E";  # alice  (\L starts, \E ends)
print "\U$name\E";  # ALICE
print "\u$name";     # Alice (capitalize first char)
print "\l$name";     # alice (lowercase first char)

# Title-case each word via regex
$s =~ s/(\w+)/\u\L$1/g;  # Hello World

sprintf 与 printf

printf 打印格式化文本;sprintf 返回它。格式说明符:%s 字符串、%d 整数、%f 浮点、%x 十六进制、%o 八进制、%b 二进制。宽度和精度控制填充:%5d、%05d、%.2f。

perl
# printf FORMAT, LIST (prints)
printf "Name: %s, Age: %d\n", "Alice", 30;
printf "Pi: %.2f\n", 3.14159;   # 3.14
printf "%5d\n", 42;             # '   42' (right-aligned)
printf "%-5d|\n", 42;           # '42   |' (left-aligned)
printf "%05d\n", 42;            # '00042' (zero-padded)

# sprintf returns the formatted string
my $date = sprintf("%04d-%02d-%02d", 2024, 1, 15);
# "2024-01-15"

# Common specifiers: %s %d %f %x %o %b %%
printf "Hex: %x, Oct: %o, Bin: %b\n", 255, 8, 5;
08

子程序

定义子程序

参数传入 @_ 数组。shift(无参数)在子程序内从 @_ 取值。用 my ($a, $b) = @_ 解包是惯用法。无 return 时返回最后求值的表达式。

perl
sub greet {
    my $name = shift;  # default: shifts from @_
    print "Hello, $name!\n";
}
greet("Alice");  # Hello, Alice!

# Subs are global by default
sub add {
    my ($a, $b) = @_;  # unpack arguments
    return $a + $b;
}
print add(2, 3);  # 5

# Forward declaration (define later)
sub compute;
print compute(4);

参数 (@_)

@_ 保存所有参数。传入的数组/哈希会扁平化丢失身份(变成扁平列表),因此嵌套结构传引用。命名参数(哈希)灵活且自解释。

perl
sub sum {
    my $total = 0;
    $total += $_ for @_;  # iterate all args
    return $total;
}
print sum(1, 2, 3, 4);  # 10

# Named arguments via hash
sub info {
    my %args = @_;
    print "Name: $args{name}\n";
    print "Age:  $args{age}\n";
}
info(name => "Alice", age => 30);

# Pass arrays/hashes by reference
sub total {
    my $arr = shift;
    my $sum = 0;
    $sum += $_ for @$arr;
    return $sum;
}

返回值

子程序可返回标量、列表或引用。返回列表让调用者解构。无显式 return 时,最后表达式被返回(按调用者上下文)。

perl
# Single return value
sub square { return $_[0] ** 2; }

# Return a list
sub min_max {
    my @nums = @_;
    my ($min, $max) = ($nums[0], $nums[0]);
    for (@nums) {
        $min = $_ if $_ < $min;
        $max = $_ if $_ > $max;
    }
    return ($min, $max);
}
my ($lo, $hi) = min_max(3, 1, 4, 1, 5);

# Implicit return (last expression)
sub name { "Alice" }  # returns "Alice"

# Void context (no return needed)
sub log_msg { print "log: $_[0]\n"; }

上下文与 wantarray

wantarray 在列表上下文返回真,标量上下文返回假(但已定义),空上下文返回 undef。这让子程序根据返回值被使用的方式表现不同(强大的 Perl 惯用法)。

perl
sub get_data {
    if (wantarray) {
        return (1, 2, 3);        # list context
    } elsif (defined wantarray) {
        return "scalar";         # scalar context
    } else {
        return;                  # void context
    }
}

my @list   = get_data();  # (1, 2, 3)
my $scalar = get_data();  # "scalar"
get_data();               # void

# localtime is context-sensitive
my $t = localtime();   # scalar: "Mon Jan  1 12:00:00 2024"
my @t = localtime();   # list: (sec, min, hour, mday, ...)

# scalar() forces scalar context
print scalar(localtime());

匿名子程序

匿名子程序(sub { ... })创建代码引用,是一等值,可存储、传递和调用。闭包捕获其封闭作用域的词法变量并保持其存活,实现有状态的回调。

perl
# Anonymous sub (coderef)
my $greet = sub {
    my $name = shift;
    return "Hi, $name";
};

print $greet->("Alice");   # Hi, Alice (preferred)
print &$greet("Bob");      # older syntax

# Closure: captures outer variable
sub make_counter {
    my $count = 0;
    return sub { return ++$count; };
}

my $c = make_counter();
print $c->();  # 1
print $c->();  # 2 (count persists)

# Pass subs as arguments
sub apply { my ($f, $x) = @_; return $f->($x); }
print apply(sub { $_ * 2 }, 21);  # 42

命名参数与默认值

把命名参数收集到哈希,用 //(defined-or)设默认值。可混合位置参数与命名参数,把哈希放最后。对选项集合常返回引用。

perl
sub create_user {
    my %args = @_;
    # Apply defaults with defined-or
    $args{role}   //= 'user';
    $args{active} //= 1;
    return \%args;
}

my $user = create_user(
    name  => 'Alice',
    email => '[email protected]',
);
print $user->{role};   # user (default applied)

# Mix positional + named (named must come last)
sub point {
    my ($x, $y, %opts) = @_;
    print "($x, $y) color=$opts{color}\n";
}
point(1, 2, color => 'red');
09

文件 I/O

打开文件

始终用带词法文件句柄的三参数 open。中间参数是模式:< 读、> 写(截断)、>> 追加。用 'or die' 检查并包含 $! 获取系统错误信息。

perl
# 3-argument open (recommended)
open(my $fh, '<', 'input.txt')
    or die "Cannot open input.txt: $!";

# Read the whole file
my @lines = <$fh>;
close $fh;

# Modes: < read, > write (truncate), >> append,
#        +< read/write, +> read/write (truncate)
open(my $out, '>', 'output.txt')
    or die "Cannot write: $!";

open(my $app, '>>', 'log.txt')
    or die "Cannot append: $!";

读取文件

大文件按行迭代(节省内存)。要一次读入整个文件,localize $/(记录分隔符)再读一次。$. 跟踪跨读取的当前行号。

perl
open(my $fh, '<', 'file.txt') or die $!;

# Line by line (memory efficient)
while (my $line = <$fh>) {
    chomp $line;
    print "Read: $line\n";
}

# Slurp entire file (undef the record separator)
local $/;
my $content = <$fh>;

# Read all lines into an array
my @lines = <$fh>;
close $fh;

# $. holds the current line number
open(my $f2, '<', 'file.txt') or die $!;
while (<$f2>) { print "$.: $_"; }

写入文件

print 和 printf 把文件句柄作为第一个参数(不带逗号)。输出有缓冲;启用 autoflush($| = 1)立即写(便于日志和管道)。close() 自动刷新。

perl
open(my $fh, '>', 'out.txt') or die $!;

print $fh "Hello, File!\n";
print $fh "Second line\n";

# Formatted write
printf $fh "%d: %s\n", 1, "first";

# Autoflush (no buffering)
use IO::Handle;
$fh->autoflush(1);

# Or via select
my $old = select($fh); $| = 1; select($old);

close $fh;  # flushes and closes

钻石运算符

钻石运算符 <> 读取 @ARGV 中列出的文件,若 @ARGV 为空则读 STDIN。配合 -i / $^I 可实现类似 sed -i 的就地编辑。$ARGV 保存当前文件名。

perl
# <> reads from files in @ARGV, or STDIN if none
while (<>) {
    print "$.: $_";
}

# Equivalent to: while (my $line = <ARGV>) { ... }

# Read all of stdin (or all arg files)
my @input = <>;

# Specify files programmatically
@ARGV = ('file1.txt', 'file2.txt');
while (<>) {
    chomp;
    print ">> $_\n";
}

# In-place editing
$^I = '.bak';  # backup extension
while (<>) { s/foo/bar/g; print; }

文件句柄

优先用词法文件句柄(my $fh),出作用域时自动关闭。裸字句柄是全局的,可能冲突。三个标准句柄 STDIN/STDOUT/STDERR 预先打开。

perl
# Lexical filehandle (modern, recommended)
open(my $fh, '<', $file) or die $!;

# Bareword filehandle (legacy, avoid)
open(IN, '<', $file) or die $!;
my $line = <IN>;
close IN;

# Standard handles: STDIN, STDOUT, STDERR
print STDOUT "to stdout\n";
my $err = <STDIN>;
warn "to stderr\n";   # STDERR is unbuffered

# Duplicate a handle
open(my $log, '>&', STDERR) or die $!;

# Open a filehandle to a string (in-memory)
open(my $mem, '>', \my $buffer) or die $!;
print $mem "stored\n";

File::Slurper

File::Slurper 是现代高效的整文件读写方式,正确处理编码和 binmode,优于手写的 slurp 代码。新代码中优于 do { local $/; <$fh> }。

perl
use File::Slurper qw(read_text write_text append_text read_lines);

# Read entire file as a string
my $text = read_text('file.txt');

# Write a string to a file
write_text('out.txt', "Hello\n");

# Append to a file
append_text('log.txt', "New entry\n");

# Read all lines (without newlines by default)
my @lines = read_lines('data.csv');

# Binary files
use File::Slurper qw(read_binary write_binary);
my $bytes = read_binary('image.png');
write_binary('copy.png', $bytes);
10

目录操作

opendir/readdir/closedir

opendir/readdir/closedir 对应目录版的 open/readline/close。readdir 返回所有条目,包括 . 和 ..(用 /^\.\.?$/ 过滤)。用文件测试运算符判断每个条目类型。

perl
opendir(my $dh, '.') or die "Cannot open dir: $!";

while (my $entry = readdir($dh)) {
    next if $entry =~ /^\./;   # skip dotfiles
    print "$entry\n";
}

# Or get all entries at once
rewinddir($dh);
my @entries = readdir($dh);

closedir $dh;

# Filter to only subdirectories
opendir(my $d, '.') or die $!;
my @dirs = grep { -d $_ } readdir($d);
closedir $d;

glob

glob 展开 shell 风格通配符(* ? [])并返回匹配的文件名。<pattern> 形式看起来像 readline,但遇到通配模式会触发 glob。File::Glob 提供带额外标志的 bsd_glob。

perl
# glob expands filename wildcards
my @files = glob('*.txt');
my @perl  = glob('*.pl *.pm');

# Angle-bracket form (legacy)
my @same = <*.txt>;

# With a path
my @logs = glob('/var/log/*.log');

# Recursive globbing
use File::Glob qw(:bsd_glob);
my @all = bsd_glob('**/*.pm');  # needs :bsd_glob

# Capture flags: GLOB_ERR, GLOB_MARK (trailing / on dirs)
my @marked = bsd_glob('*', GLOB_MARK);

mkdir/rmdir/chdir

mkdir/rmdir 处理单个目录(rmdir 要求为空)。File::Path 的 make_path/remove_tree 处理嵌套结构。chdir 改变工作目录;用 Cwd::getcwd 读取它。

perl
# Create a directory (default perms modified by umask)
mkdir 'newdir' or die $!;
mkdir 'newdir', 0755;  # explicit permissions

# Create nested directories
use File::Path qw(make_path remove_tree);
make_path('a/b/c');
make_path('x/y', { mode => 0755 });

# Remove a directory (must be empty)
rmdir 'emptydir' or die $!;

# Remove recursively
remove_tree('a');

# Change working directory
chdir '/tmp' or die $!;

# Get current directory
use Cwd qw(getcwd);
print getcwd();

File::Find

File::Find 递归遍历目录树。在 wanted 子程序内,$_ 是基名,$File::Find::name 是完整路径。finddepth 做后序遍历(便于删除)。

perl
use File::Find;

my @found;
find(sub {
    return if -d;                       # skip directories
    push @found, $File::Find::name
        if /\.pm\z/;
}, '.');

# finddepth visits directories after their contents
finddepth(sub {
    unlink $_ if -f and -M $_ > 30;     # delete old files
}, '/tmp/old');

# Multiple starting directories
find(\&wanted, qw(src lib t));

sub wanted {
    my $path = $File::Find::name;
    print "$path\n" if -f $path;
}

Path::Tiny

Path::Tiny 是现代、人性化的文件和路径操作模块,替代许多 File::Spec/File::Path 惯用法。方法链式调用自然,并以有用信息报告错误。

perl
use Path::Tiny;

# Create a path object
my $path = path('/tmp/test.txt');

# File operations
$path->touch;
$path->spew("content\n");
my $content = $path->slurp;
my @lines   = $path->lines;

# Path manipulations
my $parent = $path->parent;
my $child  = $path->child('sub.txt');
my $abs    = $path->absolute;
my $rel    = $abs->relative('/tmp');

# Iterate directory contents
for my $f (path('.')->children) {
    print $f, "\n" if $f->is_file;
}

File::Copy 与 unlink

File::Copy 提供 copy 和 move,可跨文件系统。File::Spec 构建可移植路径,无需硬编码分隔符。unlink 删除文件(返回删除数);不能删除目录。

perl
use File::Copy qw(copy move);

# Copy a file
copy('src.txt', 'dst.txt') or die $!;
copy('src.txt', 'dst.txt', 8192);  # buffer size

# Move / rename
move('old.txt', 'new.txt') or die $!;

# Portable paths via File::Spec
use File::Spec;
my $path = File::Spec->catfile('dir', 'file.txt');
my ($vol, $dir, $file) = File::Spec->splitpath($path);

# Delete files
unlink qw(a.txt b.txt c.txt);
my $removed = unlink grep { -f } glob('*.tmp');
11

引用

创建引用

反斜杠运算符创建对现有变量的引用。ref() 返回类型(SCALAR、ARRAY、HASH、CODE、...),非引用返回空字符串。被 bless 的引用返回类名。

perl
my $scalar = 42;
my @arr = (1, 2, 3);
my %h   = (a => 1);

# Backslash creates a reference
my $sref = \$scalar;
my $aref = \@arr;
my $href = \%h;
my $cref = \&some_sub;

# Check what a reference points to
print ref($aref);  # ARRAY
print ref($href);  # HASH
print ref($sref);  # SCALAR
print ref($cref);  # CODE

# A non-reference returns ''
print ref("hi");   # (empty string)

解引用

在引用前加其 sigil(@、%、$)解引用整体,或用箭头运算符 -> 访问单个元素。块形式 @{ ... } 在引用是表达式时更清晰。

perl
my $aref = [1, 2, 3];
my $href = { a => 1, b => 2 };

# Whole array
my @arr = @$aref;          # or @{ $aref }
my $len = scalar @$aref;

# Single element via arrow
print $aref->[0];          # 1
print $#$aref;             # last index (2)

# Whole hash
my %h = %$href;
my @keys = keys %$href;

# Single value via arrow
print $href->{a};          # 1

# Block form for clarity
my @arr2 = @{ $aref };

匿名数组与哈希

[ ] 创建匿名数组引用,{ } 创建哈希引用,sub { } 创建代码引用。这些可直接构建复杂的嵌套数据结构,无需中间命名变量。

perl
# Anonymous array reference: [ ]
my $arr = [1, 2, 3];
print $arr->[0];  # 1

# Anonymous hash reference: { }
my $h = { name => 'Alice', age => 30 };
print $h->{name};

# Nested anonymous structures
my $data = {
    users => [ { name => 'a' }, { name => 'b' } ],
    count => 2,
};

# Anonymous subroutine
my $code = sub { print "hi\n" };

嵌套结构

引用让你构建任意嵌套结构。两个下标之间的箭头可省略:$arr->[0][1] 等于 $arr->[0]->[1]。自动激活在赋值时创建中间引用。

perl
# Array of arrays (2D)
my $matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
];
print $matrix->[1][2];  # 6 (second arrow optional)

# Hash of hashes
my $config = {
    db  => { host => 'localhost', port => 3306 },
    app => { name => 'MyApp',     debug => 1 },
};
print $config->{db}{host};  # localhost

# Auto-vivification creates intermediate refs
my %tree;
$tree{a}{b}{c} = 1;  # builds the path automatically

箭头运算符

箭头 -> 解引用:$ref->[idx] 用于数组,$ref->{key} 用于哈希,$ref->() 用于代码,$obj->method 用于对象。连续下标之间的箭头可省略以提高可读性。

perl
my $arr = [[1, 2], [3, 4]];
my $h   = { users => [{ name => 'a' }] };

# Arrow between subscripts (second is optional)
print $arr->[0][1];           # 2
print $h->{users}[0]{name};   # a

# Arrow for coderef calls
my $greet = sub { "hi $_[0]" };
print $greet->("Bob");        # hi Bob

# Method calls on objects
$obj->method($arg);

# Slicing a dereferenced array
my @vals = @{ $h->{users} };

引用类型

ref() 对被 bless 的引用(对象)返回类名,可能隐藏底层类型。Scalar::Util::reftype() 始终返回真实结构类型。用 weaken() 打破引用循环。

perl
use Scalar::Util qw(reftype weaken);

my $aref = [];
my $href = {};
my $cref = sub {};

print ref($aref);       # ARRAY
print reftype($aref);   # ARRAY

# A blessed reference (object)
my $obj = bless {}, 'MyClass';
print ref($obj);        # MyClass
print reftype($obj);    # HASH  (the underlying type)

# Type checks
if (ref($r) eq 'ARRAY') { ... }
if (ref($r) eq 'HASH')  { ... }

# weaken avoids circular-reference memory leaks
weaken($r);
12

模块

use 与 require

use 在编译时运行并调用 import()(使导出名称可用)。require 在运行时运行且不导入。条件或延迟加载用 require,正常情况用 use。

perl
# use: runs at compile time, calls the module's import()
use strict;
use warnings;
use Data::Dumper;

# require: runs at runtime, does NOT call import()
require MyModule;

# Conditional loading (runtime)
if ($DEBUG) {
    require Carp;
    Carp->import('confess');
}

# use with version and import list
use v5.36;
use List::Util 1.33 qw(first sum max);

# use MODULE VERSION LIST
use Data::Dumper qw(Dumper);

use lib

@INC 是 Perl 搜索模块的目录列表。'use lib' 在编译时前置目录。FindBin 找到脚本所在目录,便于定位随脚本分发的模块。

perl
# Add directories to the module search path
use lib '/home/me/lib';
use lib 'lib', 'local/lib';

# Locate a path relative to the script
use FindBin;
use lib "$FindBin::Bin/lib";

# Inspect the search path
print join("\n", @INC);

# Add at runtime (then require)
push @INC, '/path/to/lib';
require MyModule;

# Remove an entry
@INC = grep { !/local/ } @INC;

Exporter

Exporter 控制模块导出什么。@EXPORT 强制导出(通常不鼓励);@EXPORT_OK 让调用者按需导入(推荐)。%EXPORT_TAGS 分组导出便于像 ':all' 这样导入。

perl
package MyModule;
use parent 'Exporter';

our @EXPORT    = qw(func1);          # exported by default
our @EXPORT_OK = qw(func2 func3);    # exported on request
our %EXPORT_TAGS = (
    all => [qw(func1 func2 func3)],
);

sub func1 { return "one"; }
sub func2 { return "two"; }
sub func3 { return "three"; }

1;  # a module must return a true value

# Usage in another file:
#   use MyModule;             # gets func1
#   use MyModule qw(func2);   # gets func2 only
#   use MyModule ':all';      # gets all three

BEGIN 与 END

BEGIN 块在编译时运行(用于必须在其余代码编译前完成的设置)。END 块在程序退出时运行(用于清理)。CHECK 和 INIT 在编译与运行之间运行。

perl
# BEGIN runs at compile time
BEGIN {
    print "Compiled\n";
    require MyModule;
}

# END runs at exit (in LIFO order)
END {
    print "Cleaning up\n";
}

# Other phases: UNITCHECK, CHECK, INIT
INIT { print "Running\n"; }

# Compile-time constant definition
use constant DEBUG => 1;

# BEGIN to push a custom lib path early
BEGIN { push @INC, '/custom' }

package

package 声明命名空间。模块文件(MyModule.pm)通常包含一个与其名匹配的 package。文件必须以真值(1;)结束,以便 'use' 知道它加载成功。

perl
package MyModule;
use strict;
use warnings;

our $VERSION = '1.00';

sub new {
    my $class = shift;
    return bless {}, $class;
}

sub method {
    my $self = shift;
    return "called";
}

1;  # required: modules must return a true value

# Switch back to the main package
package main;
my $obj = MyModule->new;
print $obj->method;

常量

use constant 创建可内联的常量(无 sigil)。Readonly 和 Const::Fast 提供词法作用域的不可变标量/数组/哈希。常量本质是带 () 原型的子程序,可被内联。

perl
# use constant (compile-time)
use constant {
    PI    => 3.14159,
    MAX   => 100,
    DEBUG => 0,
};
print PI;  # 3.14159

# Readonly (lexically scoped, immutable)
use Readonly;
Readonly my $NAME => 'Alice';

# Const::Fast
use Const::Fast;
const my $count => 5;

# A constant is just a sub with an empty prototype
sub MAX_CONNECTIONS () { 10 }
print MAX_CONNECTIONS;  # 10
13

面向对象编程

package 与 bless

Perl OOP 是显式的:类是 package,对象是被 bless 的引用。bless 把引用关联到类,使方法分派生效。构造函数(惯例叫 new)构建并 bless 对象。

perl
package Animal;
use strict;
use warnings;

sub new {
    my ($class, %args) = @_;
    my $self = { name => $args{name}, sound => 'generic' };
    return bless $self, $class;
}

sub name { shift->{name} }

1;

# bless associates a reference with a package (the class).
# $self is a hash ref blessed into Animal.

构造函数 (new)

构造函数惯例命名为 new,但只是普通子程序。第一个参数是类名。用 Class->new(args)(箭头语法);间接语法(new Class)不鼓励,因为可能误解析。

perl
package Point;
use strict;
use warnings;

sub new {
    my ($class, $x, $y) = @_;
    my $self = { x => $x, y => $y };
    return bless $self, $class;
}

# Usage (arrow syntax preferred)
my $p = Point->new(3, 4);

# Indirect syntax ( discouraged)
my $p2 = new Point(3, 4);

# Named-argument constructor
sub new_named {
    my ($class, %args) = @_;
    return bless \%args, $class;
}

方法

方法是第一个参数为调用者的子程序(实例方法为 $self,类方法为类名)。用 shift 取出它,再解包其余参数。setter 常返回 $self 以便链式调用。

perl
package Point;
sub new { ... }

# Instance method: first arg is the object
sub distance {
    my ($self, $other) = @_;
    my $dx = $self->{x} - $other->{x};
    my $dy = $self->{y} - $other->{y};
    return sqrt($dx**2 + $dy**2);
}

# Getter
sub x { my $self = shift; return $self->{x}; }

# Setter (chainable)
sub set_x {
    my ($self, $x) = @_;
    $self->{x} = $x;
    return $self;
}

# Class method
sub count { our $count; return ++$count }

继承 (use parent)

use parent 干净地设置 @ISA 并调用父类的 import。用 SUPER::method 调用被覆盖的父类方法。支持多重继承,但方法解析顺序(MRO)可能微妙;用 mro::get_linear_isa 查看。

perl
package Dog;
use parent 'Animal';

sub new {
    my ($class, %args) = @_;
    my $self = $class->SUPER::new(%args);  # call parent
    $self->{breed} = $args{breed};
    return $self;
}

sub speak {
    my $self = shift;
    return "Woof!";
}

# Direct @ISA (lower-level)
package Cat;
our @ISA = ('Animal');

# Multiple inheritance
package Duck;
use parent qw(Flyable Animal);

访问器

可手写访问器(一个子程序同时处理 get 和 set)。Class::Accessor、Class::Tiny、Moo 或 Moose 等模块生成它们,减少样板。选择取决于你的依赖预算。

perl
# Hand-written accessor (getter + setter)
sub name {
    my $self = shift;
    if (@_) { $self->{name} = shift; return $self; }
    return $self->{name};
}

# Class::Accessor generates them
package User;
use parent 'Class::Accessor';
__PACKAGE__->mk_accessors(qw(name email age));

# Usage
my $u = User->new({ name => 'Alice' });
$u->name;          # get
$u->name('Bob');   # set

# Moo / Moose generate accessors declaratively
has name => (is => 'rw');

DESTROY 与 AUTOLOAD

DESTROY 在对象引用计数归零时自动调用(用于清理如关闭句柄)。AUTOLOAD 拦截对未定义方法的调用,实现动态访问器或委托,但慎用。

perl
package FileHandle::Custom;
sub new {
    my ($class, $file) = @_;
    open(my $fh, '<', $file) or die $!;
    return bless { fh => $fh }, $class;
}

sub DESTROY {
    my $self = shift;
    close $self->{fh} if $self->{fh};
    # auto-called when refcount reaches 0
}

# AUTOLOAD catches undefined method calls
sub AUTOLOAD {
    my $self = shift;
    our $AUTOLOAD;
    my $method = $AUTOLOAD;
    $method =~ s/.*:://;
    return $self->{$method};  # treat as accessor
}
14

文件测试运算符

存在性与类型

文件测试运算符(-e、-f、-d 等)在一次 stat 调用中检查属性。堆叠(-f -e $f)共享一次 stat。_ 运算符复用最近一次 stat/lstat/文件测试的结果以提升效率。

perl
my $f = 'file.txt';

print "exists\n"     if -e $f;   # exists
print "regular\n"    if -f $f;   # plain file
print "dir\n"        if -d $f;   # directory
print "symlink\n"    if -l $f;   # symbolic link
print "socket\n"     if -S $f;   # socket
print "pipe\n"       if -p $f;   # named pipe (FIFO)
print "char dev\n"   if -c $f;   # character device
print "block dev\n"  if -b $f;   # block device

# Stack tests (uses the same stat cache)
print "regular file\n" if -f -e $f;

# _ reuses the last stat result
print "size: ", -s _ if -e $f;

权限 (-r/-w/-x)

-r/-w/-x 检查有效用户的权限(用 -R/-W/-X 检查真实用户)。-u/-g/-k 检测 setuid、setgid 和粘滞位。这些反映实际文件系统状态,不只是模式位。

perl
my $f = 'script.pl';

print "readable\n"   if -r $f;  # effective uid can read
print "writable\n"   if -w $f;  # effective uid can write
print "executable\n" if -x $f;  # effective uid can exec
print "owned\n"      if -o $f;  # owned by effective uid

# Real uid variants (rarely needed)
print "real read\n"  if -R $f;
print "real write\n" if -W $f;
print "real exec\n"  if -X $f;

# setuid / setgid / sticky
print "setuid\n"  if -u $f;
print "setgid\n"  if -g $f;
print "sticky\n"  if -k $f;

大小 (-s)

-s 返回文件大小字节数(或 undef)。-z 测试零大小;-s 在布尔上下文测试非零。其余信息(uid、gid、时间)用 stat(),它返回 13 元素列表。

perl
my $f = 'data.bin';

# -s returns size in bytes (or undef if missing)
my $size = -s $f;
print "Size: $size bytes\n";

# Empty file
if (-z $f) { print "empty\n"; }
# Non-empty (size > 0)
if (-s $f) { print "has data\n"; }

# Human-readable size
use Number::Bytes::Human qw(format_bytes);
print format_bytes(-s $f), "\n";

# stat() for full detail
my @st = stat($f);
print "Size: $st[7] bytes\n";  # index 7 is size

时间 (-M/-A/-C)

-M、-A、-C 返回相对于 $^T(脚本开始时间)的文件年龄(天),因此旧文件值为正。要取 epoch 秒,用 stat() 索引 9(mtime)、8(atime)、10(ctime)。

perl
my $f = 'file.txt';

# All return age in days since script start ($^T)
print -M $f;  # modification age (days)
print -A $f;  # access age (days)
print -C $f;  # inode-change age (days)

# Older than 30 days?
if (-M $f > 30) { print "old file\n"; }

# Modified today?
if (-M $f < 1) { print "modified today\n"; }

# Epoch seconds via stat
my @st = stat($f);
my $mtime = $st[9];   # mtime (epoch)
my $atime = $st[8];   # atime (epoch)
my $ctime = $st[10];  # ctime (epoch)

文本/二进制 (-T/-B)

-T/-B 通过检查首字节(空字节、控制字符)启发式判断文本还是二进制。空文件算文本。要真正的 MIME 类型,用 File::MimeInfo::Magic 等模块。

perl
my $f = 'file.txt';

# -T text file, -B binary file
if (-T $f) { print "text\n"; }
if (-B $f) { print "binary\n"; }

# Perl heuristically decides from the first block

# Filter a directory to text files only
opendir(my $dh, '.');
my @text_files = grep { -f && -T } readdir($dh);
closedir $dh;

# Note: empty files count as text

# Proper MIME detection
use File::MimeInfo::Magic;
print mimetype($f);

stat 与 lstat

stat() 返回 13 个字段;最常用的是 size(7)、mode(2)、mtime(9)。lstat() 类似 stat 但不跟随符号链接(可检查链接本身)。_ 运算符为链式测试复用缓存结果。

perl
my $f = 'file.txt';

# stat returns a 13-element list
my @s = stat($f);
# 0 dev, 1 ino, 2 mode, 3 nlink, 4 uid, 5 gid,
# 6 rdev, 7 size, 8 atime, 9 mtime, 10 ctime,
# 11 blksize, 12 blocks

my $size  = $s[7];
my $perms = sprintf "%04o", $s[2] & 07777;

# lstat does NOT follow symlinks
my @ls = lstat($f);

# stat on an open filehandle
my @sf = stat($fh);

# _ reuses the last stat result (no new syscall)
print -s _ if -e $f;
15

CPAN

cpanm (App::cpanminus)

cpanm(App::cpanminus)是最流行的 CPAN 客户端:快速、轻量、零配置。自动解析并安装依赖。-l 安装到本地目录;--installdeps 读取 cpanfile。

perl
# Install a module from the command line:
#   cpanm DBI
#   cpanm Mojolicious
#   cpanm --interactive Module::Name

# Install a specific version range
#   cpanm DBI~">=1.6"

# Install from a git repository
#   cpanm https://github.com/user/Mod.git

# Install dependencies from a cpanfile
#   cpanm --installdeps .

# Install into a local directory
#   cpanm -l extlib DBI

# Verbose output
#   cpanm -v DBI

cpanfile

cpanfile 按阶段(runtime、test、develop)和关系(requires、recommends、suggests、conflicts)列出依赖。cpanm、Carton、cpm 等工具读取它安装正确模块。

perl
# cpanfile declares runtime and test dependencies
requires 'perl', '5.014';
requires 'DBI'          => '1.6';
requires 'Mojolicious'  => '9.0';

# Phase-specific dependencies
on develop => sub {
    requires 'Test::Pod';
    requires 'Test::Perl::Critic';
};

on test => sub {
    requires 'Test::More'   => '0.98';
    requires 'Test::Exception';
};

recommends 'JSON::XS';
conflicts 'Old::Module';

Carton

Carton 读取 cpanfile,把依赖安装到 local/,并写 cpanfile.snapshot 锁定确切版本,实现可重现部署。用 'carton exec' 在本地环境中运行程序。

perl
# Carton manages a locked dependency tree
# Install Carton:  cpanm Carton

# Commands (shell):
#   carton install        # install from cpanfile
#   carton install DBI    # add a module
#   carton exec ./app.pl  # run with local deps
#   carton bundle         # vendor tarballs
#   carton show           # list installed

# cpanfile.snapshot locks exact versions
# local/ holds the installed modules

# In your code, ensure the lib path:
use lib 'local/lib/perl5';
use DBI;

安装模块

可在任意位置安装模块并用 'use lib' 添加路径。local::lib 引导用户级模块目录。Module::Util 定位已安装模块路径;CLI 上用 -M 加载模块做单行操作。

perl
# Install into a local directory
#   cpanm -l extlib DBI

# Then use it in your script:
use lib 'extlib/lib/perl5';
use DBI;

# Bootstrap local::lib
#   cpanm --local-lib=~/perl5 local::lib
use local::lib '~/perl5';

# Find a module's installed path
use Module::Util qw(module_path);
my $path = module_path('DBI');

# Check an installed version from the CLI:
#   perl -MDBI -e 'print $DBI::VERSION'

perlbrew

perlbrew 让你在主目录安装并切换多个 Perl 版本,不影响系统 Perl。'perlbrew use' 设置当前 shell 的 perl;'switch' 设置默认。

perl
# App::perlbrew manages multiple perl installations
# Shell commands:
#   perlbrew init
#   perlbrew install perl-5.38.0
#   perlbrew switch perl-5.38.0
#   perlbrew use perl-5.36.0
#   perlbrew list
#   perlbrew install-cpanm

# Install a CPAN module into a specific perl
#   perlbrew exec --with perl-5.38 cpanm DBI

# Use a specific perl in a script's shebang:
#   #!/home/user/perl5/perlbrew/perls/perl-5.38/bin/perl
use v5.38;
use strict;
use warnings;

Local::lib

local::lib 设置环境变量(PATH、PERL5LIB 等),使模块安装到私有目录而无需 root。脚本中用 'use local::lib',或在 shell rc 文件中 eval 引导行。

perl
# Bootstrap local::lib (shell):
#   cpanm --local-lib=~/perl5 local::lib

# In your script, use the local library:
use local::lib;

# Or specify a path explicitly
use local::lib '/path/to/libs';

# Set up environment in your shell rc:
#   eval "$(perl -I$HOME/perl5/lib/perl5 -Mlocal::lib)"

# Verify @INC includes your local path
print "$_\n" for @INC;

# Now install modules locally:
#   cpanm -l ~/perl5 Some::Module
16

异常处理

die

die 抛出沿调用栈传播的异常。系统错误请包含 $!。结尾的 \n 抑制自动追加的 'at file line N'。带引用的 die 抛出对象异常。

perl
# die throws an exception
open(my $fh, '<', 'missing.txt')
    or die "Cannot open: $!";  # $! is the system error

# A trailing newline suppresses "at file line N"
die "Custom error\n";

# die with a reference (object exception)
die MyException->new(message => 'oops');

# Default die appends location info
die "Something bad happened";
# prints: Something bad happened at script.pl line 3.

warn

warn 向 STDERR 打印消息但不终止。用 'no warnings' 禁用特定警告类别。%SIG{__WARN__} 钩子可全局拦截、记录或抑制警告。

perl
# warn prints to STDERR but continues
warn "This is a warning: $!";

# Trailing newline omits location
warn "Just a note\n";

# Disable a warning category locally
{
    no warnings 'uninitialized';
    print $undefined;  # no warning
}

# Custom __WARN__ handler
$SIG{__WARN__} = sub {
    my $msg = shift;
    log_warning($msg);
    print STDERR $msg;
};

eval

eval BLOCK 捕获其内部抛出的异常(die 变为非致命错误,捕获到 $@)。优先用 eval BLOCK 而非 eval STRING,后者在运行时编译字符串,对不可信输入有安全风险。

perl
# eval BLOCK (modern, recommended)
eval {
    risky_operation();
    die "failed";
};
if ($@) {
    print "Caught: $@\n";
}

# eval STRING parses and runs code (avoid; slow & unsafe)
eval '\$x = 1 + 2';

# eval returns the last expression on success
my $result = eval { compute() };
my $error  = $@;  # check immediately

# Always check $@ right after eval
# (or use Try::Tiny for safety)

$@ 与 $!

$@ 保存最近的 eval/die 错误。$! 是 errno(双值变量:数字上下文为编号,字符串上下文为字符串)。$? 是子进程退出状态;$? >> 8 是退出码,$? & 127 是信号。$^E 是平台特定错误。

perl
# $@ - the last eval error
eval { die "boom" };
print $@ if $@;

# $! - errno (system errors)
open(my $fh, '<', 'x') or die "open: $!";

# $! in numeric vs string context
print int($!);    # errno number (e.g. 2)
print "$!";       # error string (e.g. "No such file")

# $? - child process status
system("ls");
print $? >> 8;    # exit code

# $^E - extended error (platform-specific)
print $^E;

Try::Tiny

Try::Tiny 提供干净的 try/catch/finally 语法,并修复普通 eval 的 $@ 被覆盖问题。catch 块内错误在 $_ 中(不是 $@)。finally 无论成功失败都运行。

perl
use Try::Tiny;

try {
    risky_code();
    die "explicit fail";
} catch {
    warn "caught: $_";  # $_ is the error, not $@
} finally {
    cleanup();           # always runs
};

# Return value flows through
my $val = try {
    compute_value();
} catch {
    default_value();
};

# Note: inside catch use $_, not $@

自定义异常

die 可抛出引用(对象)而非字符串。重载字符串化使对象打印美观。用 ref/isa 检查类型。Exception::Class 提供更完整的带字段异常层次结构。

perl
package MyException;
use overload '""' => sub { $_[0]->{message} };

sub new {
    my ($class, %args) = @_;
    return bless { message => $args{message} }, $class;
}

1;

# Usage
eval {
    die MyException->new(message => 'Custom error');
};
if (ref($@) && ref($@)->isa('MyException')) {
    print "Custom: $@\n";
}

# Or use Exception::Class for richer features
use Exception::Class ('MyEx' => { fields => ['detail'] });
MyEx->throw(detail => 'something');
17

数据库 (DBI)

连接 (DBI)

DBI 是 Perl 的数据库接口,每个引擎有独立的 DBD:: 驱动。用 RaiseError 在出错时自动 die。DSN 以 'DBI:driver:...' 开头。SQLite 的 DSN 只是 'DBI:SQLite:dbname=file'。

perl
use DBI;

my $dbh = DBI->connect(
    'DBI:mysql:database=test;host=localhost',
    'user', 'password',
    { RaiseError => 1, AutoCommit => 1 }
) or die "Connect failed: $DBI::errstr";

# Always disconnect when done
$dbh->disconnect;

# Other drivers
#   DBI:mysql:    DBI:Pg:    DBI:SQLite:    DBI:Oracle:
my $sqlite = DBI->connect('DBI:SQLite:dbname=test.db');

查询 (prepare/execute)

prepare + execute 是安全模式:一次准备,多次用不同占位符执行。fetchrow_array/fetchrow_hashref 迭代行;fetchall_arrayref 一次取全部。finish() 提前释放。

perl
my $sth = $dbh->prepare('SELECT id, name FROM users WHERE age > ?');
$sth->execute(18);

# Fetch one row at a time as an array
while (my @row = $sth->fetchrow_array) {
    print "id=$row[0], name=$row[1]\n";
}

# Fetch as a hashref
while (my $row = $sth->fetchrow_hashref) {
    print $row->{name}, "\n";
}

# Fetch all rows
my $rows = $sth->fetchall_arrayref;

$sth->finish;  # release statement handle early

获取结果

DBI 便捷方法合并 prepare+execute+fetch:selectall_arrayref(全部行)、selectrow_array(单值)、selectrow_hashref(单行)。更简洁但重复调用不如 prepare/execute 高效。

perl
# All rows as an array of arrays
my $rows = $dbh->selectall_arrayref('SELECT * FROM users');

# All rows keyed by a column
my $by_id = $dbh->selectall_hashref(
    'SELECT * FROM users', 'id'
);

# Single value
my $count = $dbh->selectrow_array('SELECT COUNT(*) FROM users');

# Single row as a hash
my $row = $dbh->selectrow_hashref(
    'SELECT * FROM users WHERE id = ?', undef, 5
);

事务

事务需要关闭 AutoCommit。在 eval 中包裹修改,成功调 commit() 或失败调 rollback()。begin_work 临时禁用 AutoCommit 执行一个事务,之后重置。

perl
# Disable AutoCommit to begin a transaction
$dbh->{AutoCommit} = 0;

eval {
    $dbh->do('INSERT INTO accounts ...');
    $dbh->do('UPDATE accounts SET balance = ...');
    $dbh->commit;
};
if ($@) {
    $dbh->rollback;
    die "Transaction failed: $@";
}

# begin_work is cleaner (auto-resets AutoCommit)
$dbh->begin_work;
eval { ...; $dbh->commit; };
$dbh->rollback if $@;

占位符

占位符(?)把 SQL 结构与数据分离,防止 SQL 注入并允许语句复用。切勿把值插值进 SQL 字符串。quote() 转义值,但占位符更干净且通常更快。

perl
# ALWAYS use placeholders to prevent SQL injection
my $sth = $dbh->prepare(
    'INSERT INTO users (name, age) VALUES (?, ?)'
);
$sth->execute('Alice', 30);
$sth->execute('Bob', 25);

# Named placeholders (some drivers)
#   :name, :age
$dbh->do('INSERT ... VALUES (:name, :age)', undef,
    { name => 'Alice', age => 30 });

# Bulk insert
my @rows = (['a', 1], ['b', 2]);
$sth->execute(@$_) for @rows;

# quote() exists but prefer placeholders
my $q = $dbh->quote("O'Brien");

错误处理

启用 RaiseError 时 DBI 出错即 die,可用 eval/Try::Tiny。未启用时检查返回值并用 $dbh->err/errstr 查看。HandleError 让你拦截错误以记录或恢复。

perl
# RaiseError: die on error (recommended)
my $dbh = DBI->connect($dsn, $u, $p,
    { RaiseError => 1, PrintError => 0 });

# Manual checking (without RaiseError)
my $rv = $dbh->do('UPDATE ...');
unless ($rv) { die $dbh->errstr; }

# Error info: $dbh->err, errstr, state
print $dbh->err;      # error code
print $dbh->errstr;   # error message
print $dbh->state;    # SQLSTATE

# HandleError callback for custom handling
$dbh->{HandleError} = sub {
    my $msg = shift;
    log_error($msg);
    return 0;  # 0 = propagate (die)
};
18

命令行与 @ARGV

@ARGV

@ARGV 包含传给脚本的命令行参数(脚本名在 $0,不在 @ARGV)。可像任何数组一样 shift、pop 或索引。许多模块(Getopt::Long)会为你消费 @ARGV。

perl
# @ARGV holds command-line args (script name NOT included)
my $file = $ARGV[0];
my @args = @ARGV;

# Number of arguments
my $n = scalar @ARGV;

# Process args one at a time
while (@ARGV) {
    my $arg = shift @ARGV;
    print "Arg: $arg\n";
}

# Capture all remaining args
my @rest = @ARGV;

# The script's own name is in $0
print "Running $0\n";

Getopt::Long

Getopt::Long 解析命令行选项,带类型说明符:=s 字符串、=i 整数、=f 浮点、@ 多值。短/长别名用 |(verbose|v)。选项从 @ARGV 移除,剩余参数保留。

perl
use Getopt::Long;

my ($verbose, $name, $count, @files, $help);
GetOptions(
    'verbose|v'  => \$verbose,
    'name=s'     => \$name,
    'count=i'    => \$count,
    'files=s@'   => \@files,
    'help|h'     => \$help,
) or die "Usage: $0 [--verbose] [--name=X]\n";

exit print_usage() if $help;
print "verbose=$verbose name=$name count=$count\n";

# Run: perl script.pl -v --name=Alice --count=3 file1 file2

钻石运算符

钻石运算符 <> 读取 @ARGV 中命名的文件(若为空则读 STDIN),让一个脚本可作 Unix 过滤器。设置 $^I 可就地编辑文件,保留备份。

perl
# <> reads from files in @ARGV, or STDIN if empty
while (my $line = <>) {
    chomp $line;
    print "processed: $line\n";
}

# Run: perl script.pl file1.txt file2.txt
# Run: cat file | perl script.pl

# $ARGV is the current filename being read
# @ARGV is consumed as files are processed

# Reset to read a new set of files
@ARGV = @new_files;
while (<>) { ... }

# In-place edit with backup
$^I = '.bak';
while (<>) { s/foo/bar/g; print; }

退出码

exit 以状态码终止程序(0 对 shell 表示成功)。system/backticks 后 $? 保存子进程状态:位 0-6 是信号,位 7 是 core dump,位 8+ 是退出码。

perl
# exit with a status (0 = success)
exit 0;    # success
exit 1;    # general error
exit 255;  # out-of-range wraps (255 == -1)

# die sets a non-zero exit
eval { die "error\n" };
exit 1 if $@;

# Check a child's exit status
system("ls");
my $code   = $? >> 8;    # exit value
my $signal = $? & 127;   # signal that killed it
my $core   = ($? & 128); # core dump flag
print "exit=$code signal=$signal\n";

# END blocks still run on exit
END { cleanup(); }

__DATA__

__DATA__ / __END__ 在单文件内分隔代码与数据;DATA 文件句柄读取其后的内容。便于嵌入测试夹具、模板或配置。模块中用 __DATA__ 以便调用者读取。

perl
# __DATA__ (or __END__) marks the end of code
# <DATA> reads from this section in the same file
while (my $line = <DATA>) {
    chomp $line;
    print "[$line]\n";
}

__DATA__
line 1
line 2
line 3

# __END__ behaves the same in a script;
# in a module use __DATA__ so the section is accessible.
# Data::Section supports multiple named sections.

单行命令

Perl 以单行命令标志闻名:-e 代码、-n/-p 循环输入、-i 就地编辑、-a 自动拆分到 @F、-F 定界符、-l 自动 chomp/换行。这些让 Perl 成为强大的 sed/awk 替代品。

perl
# -e execute code
#   perl -e 'print "hello\n"'

# -n loop over input (no auto-print)
#   perl -ne 'print if /pattern/' file.txt

# -p loop and auto-print (like sed)
#   perl -pe 's/foo/bar/g' file.txt

# -i in-place edit (with optional backup suffix)
#   perl -i.bak -pe 's/old/new/g' *.txt

# -l auto chomp + add newline on print
# -a autosplit into @F (default whitespace)
# -F set the delimiter for -a
#   perl -F, -ane 'print "$F[1]\n"' data.csv
19

格式化输出

printf

printf 打印格式化文本。说明符:%s 字符串、%d 整数、%f 浮点、%x/%o/%b 十六进制/八进制/二进制、%e 科学计数。标志:- 左对齐、+ 强制符号、0 零填充、# 备用形式。宽度和精度:%8.2f。

perl
# printf FORMAT, LIST
printf "Name: %s\n", "Alice";
printf "Age: %d\n", 30;
printf "Pi: %.2f\n", 3.14159;   # 3.14
printf "Hex: %x\n", 255;         # ff
printf "Oct: %o\n", 8;           # 10
printf "Bin: %b\n", 5;           # 101
printf "Sci: %e\n", 123456;      # 1.234560e+05

# Width and alignment
printf "%10s|\n", "hi";    # '        hi|' (right)
printf "%-10s|\n", "hi";   # 'hi        |' (left)
printf "%05d\n", 42;       # '00042' (zero-pad)
printf "%+d\n", 42;        # '+42' (force sign)

sprintf

sprintf 与 printf 相同但返回格式化字符串而非打印。用于构建字符串、日志行和表格输出。用 %% 表示字面百分号。

perl
# sprintf returns the formatted string
my $date = sprintf("%04d-%02d-%02d", 2024, 1, 15);
# "2024-01-15"

my $log = sprintf("[%s] %s: %s",
    scalar localtime, "INFO", "message");

# String padding
my $right = sprintf("%20s", "hi");
my $left  = sprintf("%-20s|", "hi");

# Multiple formatted values
my $row = sprintf("%-10s %5d %8.2f",
    "apple", 3, 1.50);

# Literal percent
my $pct = sprintf("%d%%", 50);  # "50%"

format 与 write

Perl 的报告格式功能用字段占位符定义列模板。write() 渲染当前值。它较老式,但对固定宽度报表很方便。现代代码常用 printf/sprintf 替代。

perl
# Define a report format for STDOUT
format STDOUT =
@<<<<<<<<<<<<<< @>>>>>>
$name,           $amount
.

my $name   = "Alice";
my $amount = 1234.56;
write;  # outputs the formatted line: Alice           1234.56

# Optional top-of-page (header) format
format STDOUT_TOP =
Name           Amount
----------------------
.

# $~ sets the format name, $^ sets the top-of-page format

字段占位符

字段占位符定义格式中的对齐和宽度:@< 左、@> 右、@| 居中、@# 数字。~ 抑制空行;~~ 重复直到字段耗尽;^ 启用多行文本的自动换行。

perl
# Format field holders:
# @<<<   left-justified  (min width = number of chars)
# @>>>   right-justified
# @|||   centered
# @###.## numeric (right-justified, with decimals)
# @*     multiline (takes whole lines)

format REPORT =
@<<<<<<<<<<<<<<<<  @>>>>.##
$text,              $num
.

# ~  suppresses a blank line if fields are empty
# ~~ repeats the line until a field is exhausted
format BLOCK =
~~ ^<<<<<<<<<<<<<<<<<
$text
.

# ^  enables word-wrap into the field

格式说明符

说明符把值映射为文本:%s 字符串、%d/%u 整数、%f 浮点、%e/%E 科学计数、%x/%o/%b 数字进制。标志调整格式;宽度和精度(N.M)控制字段大小和小数位数。

perl
# printf / sprintf specifiers
%%   literal percent
%s   string
%c   character (from code point)
%d   signed integer
%u   unsigned integer
%f   floating point
%e   scientific (lowercase)
%E   scientific (uppercase)
%x   hex (lowercase)    %X hex (uppercase)
%o   octal
%b   binary

# Flags: - + 0 space #
printf "%-10s|%+d|%05d| %#x", "x", 5, 5, 255;

# Width and precision: %N.M
printf "%.3f", 3.14159;   # 3.142
printf "%8.2f", 3.14159;  # '    3.14'

填充与对齐

用 %-Ns 左对齐,%Ns 右对齐,%0Nd 零填充数字。没有内置居中说明符;手动计算填充或用 tr/// 后处理把空格替换为其他填充字符。

perl
# Right-align (default for numbers)
my $r = sprintf("%10s", "hi");    # '        hi'

# Left-align with '-'
my $l = sprintf("%-10s|", "hi");  # 'hi        |'

# Zero-pad numbers
my $z = sprintf("%05d", 42);      # '00042'

# Center manually
sub center {
    my ($s, $w) = @_;
    my $pad = int(($w - length($s)) / 2);
    return ' ' x $pad . $s . ' ' x ($w - $pad - length($s));
}
print center("Title", 20);

# Replace spaces with another char
my $dashed = sprintf("%20s", "x");
$dashed =~ tr/ /-/;  # '-------------------x'

这篇内容对您有帮助吗?