Perl 完全指南 / 第 13 章:字符串处理
第 13 章:字符串处理
“字符串处理是 Perl 的看家本领”
Perl 从诞生之日起就是为文本处理而设计的。本章详细介绍 Perl 的字符串处理能力。
13.1 字符串基础
引号操作符
use strict;
use warnings;
# 单引号(不插值,不转义)
my $raw = 'C:\Users\name\n';
print $raw, "\n"; # C:\Users\name\n
# 双引号(插值,转义)
my $name = "Perl";
my $msg = "Hello, $name!\nVersion: $]\n";
# q// qq// qw// qr//
my $s1 = q(这不插值 $var);
my $s2 = qq(这会插值 $var);
my @words = qw(apple banana cherry); # 单词列表
my $regex = qr/\d{4}-\d{2}-\d{2}/; # 预编译正则
引号分隔符
# 可以使用各种分隔符
my $s1 = q{花括号};
my $s2 = q[方括号];
my $s3 = q(圆括号);
my $s4 = q/斜杠/;
my $s5 = q!感叹号!;
13.2 Heredoc 语法
# << 标记(双引号插值)
my $html = <<HTML;
<html>
<head><title>$name</title></head>
<body>
<h1>Hello, $name!</h1>
</body>
</html>
HTML
# <<' 标记'(单引号不插值)
my $sql = <<'SQL';
SELECT id, name, email
FROM users
WHERE age > 18
AND status = 'active'
ORDER BY name
SQL
# 缩进 heredoc(Perl 5.26+)
my $text = <<~'END';
这段文字
的前导空格
会被移除
END
Heredoc 注意事项
# 结束标记必须独占一行,前面不能有空格
my $msg = <<END;
内容
END # ← 正确
# END # ← 错误!前面有空格
# 在函数参数中使用
print <<END;
第一行
第二行
END
# 多个 heredoc
my ($a, $b) = (<<A, <<B);
Hello
A
World
B
13.3 sprintf — 格式化输出
# 基本格式
printf "字符串: %-20s\n", "left"; # 左对齐
printf "字符串: %20s\n", "right"; # 右对齐
printf "整数: %d\n", 42;
printf "浮点: %.2f\n", 3.14159;
printf "十六进制: 0x%08X\n", 255;
printf "八进制: %o\n", 255;
printf "科学计数: %e\n", 0.001;
printf "百分比: %.1f%%\n", 85.5;
# 常用格式
my $report = sprintf "%-15s %8d %12.2f\n", "苹果", 100, 350.50;
格式规范
| 格式 | 说明 | 示例 |
|---|---|---|
%s | 字符串 | sprintf "%s", "hello" |
%d | 整数 | sprintf "%d", 42 |
%f | 浮点数 | sprintf "%.2f", 3.14 |
%e | 科学计数 | sprintf "%e", 0.001 |
%x | 十六进制(小写) | sprintf "%x", 255 → ff |
%X | 十六进制(大写) | sprintf "%X", 255 → FF |
%o | 八进制 | sprintf "%o", 255 → 377 |
%b | 二进制 | sprintf "%b", 5 → 101 |
%c | 字符 | sprintf "%c", 65 → A |
%% | 百分号字面量 | sprintf "100%%" |
格式修饰
# 宽度
sprintf "%20s", "right"; # 右对齐,宽度 20
sprintf "%-20s", "left"; # 左对齐,宽度 20
# 精度
sprintf "%.3f", 3.14159; # 3.142
# 填充
sprintf "%05d", 42; # 00042
sprintf "%'x10s", "hi"; # xxxxxxxxhi
# 参数索引
sprintf "%2\$d of %1\$d", 100, 50; # 50 of 100
13.4 常用字符串函数
my $str = "Hello, Perl World!";
# 长度
my $len = length($str); # 18
# 子串
my $sub = substr($str, 7, 4); # "Perl"
substr($str, 0, 5) = "Hi"; # 原地替换
# 查找
my $pos = index($str, "Perl"); # 7(首次出现位置)
my $rpos = rindex($str, "l"); # 16(最后出现位置)
# 大小写
my $upper = uc($str); # 全大写
my $lower = lc($str); # 全小写
my $title = ucfirst("hello"); # "Hello"
my $word = fc($str); # 大小写折叠(Perl 5.16+)
# 去除空白
my $trimmed = " hello ";
$trimmed =~ s/^\s+|\s+$//g; # 正则方式
# 更好的方式
use String::Util qw(trim);
my $clean = trim(" hello "); # "hello"
# 重复和填充
my $dashed = "-" x 40; # 40 个破折号
my $padded = sprintf "%-20s", "left"; # 左对齐填充
my $zero = sprintf "%05d", 42; # 00042
# 反转
my $rev = reverse("Hello"); # "olleH"
# 替换(非正则)
$str =~ s/old/new/g; # 正则替换
13.5 Unicode 与 UTF-8
编码基础
use Encode qw(encode decode);
# 读取 UTF-8 文件
open my $fh, '<:encoding(UTF-8)', 'data.txt' or die $!;
while (my $line = <$fh>) {
print $line; # 已解码为 Perl 内部 Unicode
}
close $fh;
# 写入 UTF-8 文件
open my $out, '>:encoding(UTF-8)', 'output.txt' or die $!;
print $out "你好,世界!\n";
close $out;
# 编码转换
my $utf8_bytes = encode('UTF-8', "中文");
my $latin1 = encode('ISO-8859-1', "café");
# 解码
my $perl_str = decode('UTF-8', $utf8_bytes);
Perl 的内部表示
use Encode qw(is_utf8);
my $str = "Hello"; # 内部标记为 UTF-8
print is_utf8($str) ? "UTF-8" : "bytes", "\n";
my $bytes = encode('UTF-8', "中文");
print is_utf8($bytes) ? "UTF-8" : "bytes", "\n"; # bytes
# Perl 内部使用 UTF-8 flag 标记字符串是字符还是字节
# 字符串操作函数对两者的行为不同
Unicode 正则
use utf8; # 源代码包含 UTF-8
# \p{...} Unicode 属性
"中文" =~ /\p{Han}+/; # 匹配汉字
"café" =~ /\p{Latin}+/; # 匹配拉丁字母
"123" =~ /\p{Digit}+/; # 匹配数字
"ABC" =~ /\p{Uppercase}+/; # 匹配大写字母
# \P{...} 取反
"Hello" =~ /\P{Han}+/; # 非汉字
UTF-8 标准 I/O
# 设置标准输入输出为 UTF-8
use open ':std', ':encoding(UTF-8)';
# 之后 print 和 <> 默认使用 UTF-8
# 或者使用命令行
# perl -CS script.pl
13.6 字符串处理技巧
CSV 解析
# 简单 CSV
my @fields = split /,/, $line;
# 处理引号内的逗号
use Text::CSV;
my $csv = Text::CSV->new({ binary => 1, auto_diag => 1 });
open my $fh, '<:encoding(UTF-8)', 'data.csv' or die $!;
while (my $row = $csv->getline($fh)) {
print join(" | ", @$row), "\n";
}
close $fh;
模板替换
my %vars = (name => "张三", age => 30);
my $template = "你好,%name%,你今年 %age% 岁。";
# 简单模板替换
$template =~ s/%(\w+)%/$vars{$1} // "%$1%"/ge;
print $template;
# 输出:你好,张三,你今年 30 岁。
13.7 业务场景:Markdown 转 HTML
#!/usr/bin/env perl
use strict;
use warnings;
sub markdown_to_html {
my ($text) = @_;
my @lines = split /\n/, $text;
my $html = "";
my $in_code = 0;
for my $line (@lines) {
# 代码块
if ($line =~ /^```/) {
$in_code = !$in_code;
$html .= $in_code ? "<pre><code>\n" : "</code></pre>\n";
next;
}
if ($in_code) {
$html .= escape_html($line) . "\n";
next;
}
# 标题
if ($line =~ /^(#{1,6})\s+(.+)/) {
my $level = length($1);
$html .= "<h$level>$2</h$level>\n";
next;
}
# 粗体和斜体
$line =~ s/\*\*(.+?)\*\*/<strong>$1<\/strong>/g;
$line =~ s/\*(.+?)\*/<em>$1<\/em>/g;
# 链接
$line =~ s/\[(.+?)\]\((.+?)\)/<a href="$2">$1<\/a>/g;
# 行内代码
$line =~ s/`(.+?)`/<code>$1<\/code>/g;
$html .= "$line\n";
}
return $html;
}
sub escape_html {
my ($text) = @_;
$text =~ s/&/&/g;
$text =~ s/</</g;
$text =~ s/>/>/g;
return $text;
}
# 使用
my $md = <<'MD';
# 标题
这是 **粗体** 和 *斜体*。
访问 [Perl 官网](https://perl.org)。
## 代码
`print "Hello"`
MD
print markdown_to_html($md);
本章小结
| 要点 | 内容 |
|---|---|
q// qq// | 单/双引号操作符 |
| Heredoc | <<MARKER 多行字符串 |
sprintf | 格式化字符串 |
substr | 子串操作 |
| Unicode | Encode 模块处理编码 |
use utf8 | 源代码 UTF-8 声明 |
\p{...} | Unicode 属性匹配 |
练习
- 使用 sprintf 生成一个格式化的表格(姓名、年龄、城市)
- 编写一个 HTML 转义函数
- 实现一个简单的模板引擎(支持
%variable%替换) - 编写 UTF-8 文件读取和写入的完整示例
- 实现一个简单的 CSV 解析器(处理引号和逗号)