Perl:tr/// 没有按照我的预期进行,而 s/// 是

Perl: tr/// is not doing what I expect whereas s/// is

我想删除某些字符串中的变音符号。 tr/// 应该可以完成工作但失败了(见下文)。我以为我遇到了 encoding/decoding 问题,但我注意到 s/// 工作正常。有人可以解释为什么吗?

这是我得到的结果示例:

my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
print "$str1\n"; # => i�iii�
$str2 =~ s/î/i/;
print "$str2\n"; # => èiü

注意tr///还修改了字符串的第一个和第三个字符,而不仅仅是中间一个。

编辑: 我使用 Ubuntu 16.04 和 Mate 桌面环境。

这符合我的预期:

use v5.10;
use utf8;
use open qw/:std :utf8/;

my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
say $str1; # èiü
$str2 =~ s/î/i/;
say $str2; # èiü

use utf8 pragma 为源代码中的文字启用 UTF-8,use open pragma 将 STDOUT 切换为 UTF-8。

如果您没有 use utf8;,但您正在使用 utf8 文本编辑器查看代码,您将无法像 perl 那样查看代码。您认为 s///tr/// 的左半部分只有一个字符,但因为它是多个字节,perl 将其视为多个字符。

你认为 perl 看到的:

my $str1 = "\xE8\xEE\xFC";
my $str2 = $str1;
$str1 =~ tr/\xEE/i/;
print "$str1\n";
$str2 =~ s/\xEE/i/;
print "$str2\n";

perl 实际看到的内容:

my $str1 = "\xC3\xA8\xC3\xAE\xC3\xBC";
my $str2 = $str1;
$str1 =~ tr/\xC3\xAE/i/;
print "$str1\n";
$str2 =~ s/\xC3\xAE/i/;
print "$str2\n";

使用 s///,因为 none 个字符是正则表达式运算符,您只是在进行子字符串搜索。您正在搜索多字符子字符串。你找到了,因为在你的 s/// 中发生的同样的事情也在你的字符串文字中发生:你认为在那里的字符实际上并不存在,但是多字符序列 .

另一方面,在 tr/// 中,多个字符不被视为一个序列,而是被视为一个集合。每个字符(字节)在找到时单独处理。这不会让您得到想要的结果,因为更改 utf8 字符串的各个字节永远不是您想要的。

事实上,您可以 运行 简单的面向 ASCII 的子字符串搜索,对 utf8 一无所知,并在 utf8 字符串上获得正确的结果,这被认为是 utf8 的一个良好的向后兼容功能,相反其他编码如 ucs2/utf16 或 ucs4.


解决方案是通过添加 use utf8; 告诉 perl 源是使用 UTF-8 编码的。您还需要对输出进行编码以匹配您的终端期望的内容。

use utf8;                             # The source is encoded using UTF-8.
use open ':std', ':encoding(UTF-8)';  # The terminal provides/expects UTF-8.
my $str1 = 'èîü';
my $str2 = $str1;
$str1 =~ tr/î/i/;
print "$str1\n";
$str2 =~ s/î/i/;
print "$str2\n";