强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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", 255ff
%X十六进制(大写)sprintf "%X", 255FF
%o八进制sprintf "%o", 255377
%b二进制sprintf "%b", 5101
%c字符sprintf "%c", 65A
%%百分号字面量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/&/&amp;/g;
    $text =~ s/</&lt;/g;
    $text =~ s/>/&gt;/g;
    return $text;
}

# 使用
my $md = <<'MD';
# 标题
这是 **粗体** 和 *斜体*。
访问 [Perl 官网](https://perl.org)。

## 代码
`print "Hello"`
MD

print markdown_to_html($md);

本章小结

要点内容
q// qq//单/双引号操作符
Heredoc<<MARKER 多行字符串
sprintf格式化字符串
substr子串操作
UnicodeEncode 模块处理编码
use utf8源代码 UTF-8 声明
\p{...}Unicode 属性匹配

练习

  1. 使用 sprintf 生成一个格式化的表格(姓名、年龄、城市)
  2. 编写一个 HTML 转义函数
  3. 实现一个简单的模板引擎(支持 %variable% 替换)
  4. 编写 UTF-8 文件读取和写入的完整示例
  5. 实现一个简单的 CSV 解析器(处理引号和逗号)

扩展阅读