跳转到内容

Perl 编程/Unicode UTF-8

来自维基教科书,开放的书籍,面向开放的世界
前一个:PSGI 索引 下一个:Perl 6

在应用程序开发的背景下,使用 UTF-8 编码的 Unicode 是在应用程序中支持多种语言的最佳方式。即使在同一个网页上也可以支持多种语言。

Unicode(通常以 UTF-8 形式出现)正在取代 ASCII 和使用 ISO-8859-1 和 Windows-1252 等 8 位“代码页”。

另请参见Perl Unicode Cookbook - 44 个在 Perl 5 中使用 Unicode 的食谱。

Unicode 是一个标准,它指定了世界上大多数书写系统的所有字符。每个字符都分配了一个唯一的码位,例如 U+0030。前 256 个码位与ISO-8859-1 相同,以便轻松地转换现有的西方/拉丁-1 文本。

要查看特定码位的属性

use Unicode::UCD 'charinfo';
use Data::Dumper;
print Dumper(charinfo(0x263a));  # U+263a

如果您查看Unicode 字符引用,您会注意到并非每个码位都分配了一个字符。此外,由于向后兼容传统编码,某些字符具有多个码位。

UTF-8 是 Unicode 的一种特定编码 - 最受欢迎的编码。其他编码包括 UTF-7、UTF-16、UTF-32 等。如果您决定使用 Unicode,您可能需要使用 UTF-8。

编码定义了每个 Unicode 码位如何映射到位和字节。在 UTF-8 编码中,前 128 个 Unicode 码位使用一个字节。这些字节值与US-ASCII 相同,如果只使用 ASCII 字符,则 UTF-8 编码和 ASCII 编码可以互换。接下来的 1,920 个码位在 UTF-8 中使用两字节编码。编码剩余的码位需要三个或四个字节。

请注意,尽管 Unicode 码位 128-255 与 ISO-8859-1 相同,但 UTF-8 对这些码位中的每一个进行不同的编码。UTF-8 使用两个字节来编码这些码位中的每一个,而 ISO-8859-1 只使用一个字节来编码该范围内的每个字符。因此,ISO-8859-1 和 UTF-8 不可互换。(如果只使用 ASCII 字符,那么它们都是可以互换的,因为 ASCII、ISO-8859-1 和 UTF-8 对前 128 个 Unicode 码位都使用相同的编码。)

因此,重申一下,使用 UTF-8,并非所有字符都编码到单个字节中(与 ASCII 和 ISO-8859-1 不同)。思考一下:这将如何影响编辑器(如 vim 或 emacs)、网页和表单、数据库、Perl 本身、Perl IO、您的 Perl 源代码(如果您想包含具有多字节编码的字符)?如果字符串包含具有多字节编码的字符,那么这将如何影响传递字符串?正则表达式仍然有效吗?

字符编码比较
字符编码 # 字符 128 个 US-ASCII 字符 接下来的 128 个字符 剩余字符
US-ASCII 128 1 字节 N/A N/A
ISO-8859-1 256 1 字节 1 字节 N/A
UTF-8 > 100,000 1 字节 2 字节 2 - 6 字节

从上表可以看出,码位 128-255 (0x80-0xff) 是您需要注意的地方。 稍后,您将发现码位 128-159 (0x80-0x9F) 甚至更棘手,因为流行的 Windows-1252 字符集(另一个每个字符一个字节的编码)在该范围内与 ISO-8859-1 不兼容。

\x{c3}\x{ae}

UTF-8“成本”多少?

[编辑 | 编辑源代码]
  • 一些函数在 Perl 中使用 UTF-8 编码的字符串时速度较慢
  • 您必须编写一些额外的 Perl 代码来确保进入 Perl 的数据被正确解码,并且离开 Perl 的数据被正确编码 - 但您在使用除平台的本地 8 位字符集(我们现在将其称为N8CS[1])之外的任何字符集时都必须这样做,这通常是 ISO-8859-1/Latin-1
  • 您必须适当地与您的数据库交互 - 它是否使用 UTF-8?
  • 您必须确保您的网页指定网页以 UTF-8 编码
  • 您可能需要进行 Web 服务器调整(如果它配置为始终提供某些特定的字符集,而不是 UTF-8)

如何使用 UTF-8?

[编辑 | 编辑源代码]

如果可能,最佳实践方法是在任何地方都使用 UTF-8。这包括网页以及由此产生的 Web 表单、数据库、HTML 模板和存储在 Perl 中的字符串。一个例外可能是您的 Perl 源代码本身。如果 N8CS 足够(即,如果您的源代码中不需要任何 UTF-8 字符或字符串),那么您的源代码不需要以 UTF-8 编码。(好的,另一个例外可能是您的 HTML 模板。如果您的模板只需要/包含 N8CS,那么它们也不需要以 UTF-8 编码。)

要在 Perl Web 应用程序中正确使用 UTF-8,以下是必须完成的操作摘要

  • 进入 Perl 的所有文本(非二进制)数据/字节(因此表单数据、数据库数据、文件读取、HTML 模板等)必须被正确解码。如果传入的文本/字节以 UTF-8 编码,则必须对其进行 UTF-8 解码。如果它们以 N8CS(通常是 ISO-8859-1)编码,则应对其进行 N8CS 解码。如果它们以其他字符集编码,则必须使用该字符集对其进行解码。
  • 所有从 Perl 输出的文本数据(因此到浏览器、数据库、文件等)必须被正确编码(编码成字节流)。STDOUT(到浏览器的输出)必须使用 UTF-8 编码。
  • 浏览器需要通过 HTTP 头部和<meta>标签被告知网页是 UTF-8 编码的。

不要使用早于 5.8.1 版本的 Perl。虽然对 UTF-8 的支持从 5.6.0 版本开始,但正则表达式在下一个版本 5.6.1 中仍然无法正常工作。5.8.1 版本增加了一些速度改进。到了 Perl 5.14,Unicode 支持在很大程度上是干净且流畅的。

在我们开始深入讨论如何使用 UTF-8 的细节之前,我们需要先定义一些术语,然后谈谈 Perl 在内部存储文本时的双重特性。

术语

[edit | edit source]

一个字符是一个逻辑实体。为了使用、存储、写入、在程序之间交换等,字符必须被编码(使用字符集)。编码将逻辑字符转换为我们在程序中可以使用的内容。根据用于编码的字符集,单个字符可能需要一个或多个字节来表示。

在引用传入或传出 Perl 程序的数据时,我们将使用字节一词。一个字节是一个字节,即 8 位。编码后的字符组成一个字节。当一个字节流进入 Perl 时,字节应该被解码(使用正确的字符集——它们被编码的字符集),以便 Perl 能够确定编码后的字节流中包含哪些逻辑字符。然后,Perl 可以将这些字符存储为字符串——一个字符序列。

二进制数据也作为字节流传入。它不应该使用字符集解码,因为它可能根本不包含任何字符,或者它除了字符之外还包含其他信息,因此无法使用字符集解码。

Perl 字符串/文本

[edit | edit source]

在内部,Perl 使用以下编码之一存储每个字符串

  • 本机编码——字节编码。它使用 N8CS[2]。这是一种每字符一个字节的编码,因此最多只能编码 255 个字符。如果 Perl 没有被指示解码(不推荐),这是所有传入文本/字节的默认编码。使用这种编码的字符串被称为字节字符串二进制字符串。除非你另行指示,否则 Perl 将认为这些字节是 ISO-8859-1,而不是你的平台编码。这是一个常见的错误。
  • UTF-8 编码——字符编码。它使用(显然)UTF-8。使用这种编码的字符串被称为字符字符串文本字符串Unicode 字符串

在创建你自己的字符串时,Perl 尽可能使用 N8CS(出于向后兼容性和效率原因)。但是,如果字符无法在 N8CS 中表示,则使用 UTF-8。换句话说,如果字符串中的所有代码点都<= 0xFF,则使用 N8CS,否则使用 UTF-8。

$native_string = "\xf1";
$native_string = "\x{00f1}";    # still N8CS, since <= 0xff
$native_string = chr(0xf1);     # still N8CS, since <= 0xff
$utf8_string = "\x{0100}";

你可以使用以下方法将 N8CS 字符串转换为 UTF-8 字符串utf8::upgrade():

$my_string = "\xf1";          # N8CS byte string (one byte is used internally to encode)
utf8::upgrade($my_string);    # UTF-8 character string now (two bytes are used internally to encode)

你的程序可以包含 Perl 的两种内部格式的字符串混合。Perl 使用“UTF8 标志”来跟踪字符串内部使用的编码。值得庆幸的是,格式/标志跟随字符串。Perl 尽可能将字符串保留在 N8CS 中。但是,当 N8CS/本机字符串与 UTF-8 字符串一起使用时,本机字符串会使用 N8CS 静默隐式解码,并升级(编码)到 UTF-8。换句话说,本机字节字符串使用本机字符集解码,然后在内部编码成 UTF-8。生成的字符字符串将设置 UTF8 标志。

UTF-8 流

[edit | edit source]

任何 Perl IO 都需要正确处理字符串/文本的解码和编码。由于世界各地使用着多种字符编码,Perl 无法正确猜测用于编码某个特定传入文本/字节的字符编码,也无法知道你想要使用哪种字符编码进行传出文本/字节。传入的 UTF-8 字节流与传入的 Windows-1252 字节流并不相同。例如,Unicode 字符 U+201c(左双引号)在 Windows-1252 中使用一个字节编码(0x93),但 UTF-8 使用三个字节对其进行编码(0xE2 0x80 0x9C)。如果你希望 Perl 正确地解释你的传入文本/字节,你必须告诉 Perl 使用哪种字符集对它们进行编码,以便它们能够被正确解码。

UTF-8 文本/字节在 Perl 程序中进出时的典型流程如下

  1. 接收外部 UTF-8 编码的文本/字节流并正确解码——即,告诉 Perl 字节使用哪种字符集进行编码(在本例中,编码是 UTF-8)。Perl 可以检查解码过程中的格式错误数据(错误编码),具体取决于你选择的解码方法。Perl 将字符串在内部存储为 N8CS 或 UTF-8,具体取决于你选择的解码方法,以及在字节流中发现的字符。(通常,字符串将在内部存储为 UTF-8。)
  2. 像往常一样处理字符串。
  3. 将字符串编码成 UTF-8 编码的字节流并输出。

1. 解码文本输入

[edit | edit source]

外部输入包括提交的 HTML 表单数据、数据库数据(例如,来自 SQL SELECT 语句)、HTML 模板、文本文件、套接字、其他程序等。如果这些内容中可能包含 UTF-8 编码的数据/文本,你必须对其进行解码。Perl 中的 UTF-8 解码涉及两个步骤

  1. 根据 UTF-8 格式规则解码文本。这可能会生成解码错误,具体取决于你选择的解码方法。使用decode()始终会导致字符串在内部存储为 UTF-8,并设置 UTF8 标志(尽管 Encode 的文档中这么说)。使用utf8::decode()可能会导致 N8CS 或 UTF-8 内部编码。如果传入文本只包含 ASCII 字符,则使用 N8CS,否则使用 UTF-8。
  2. 编码文本(这可能是一个无操作)并在内部将其存储为 N8CS 或 UTF-8。如果存储为 UTF-8,则设置 UTF8 标志。

如果你确定传入数据/字节只包含 N8CS(Perl 将其解释为 ISO-8859-1)文本,则无需显式解码它(因为 Perl 的默认内部编码是 N8CS,这是一种每字符一个字节的编码)。但是,“最佳实践”建议所有传入数据/字节都应该被显式解码——你可以显式解码 ISO-8859-1、ASCII 和许多其他字符编码。

如果你不解码,Perl 假设输入文本/字节是 N8CS 编码的,因此每个字节都被视为一个单独的字符——显然,如果你有一个多字节 UTF-8 编码的字节流/文本进入,这不是你想要的。不正确的解码会导致 双重编码,由于隐式解码(如上所述),这可能难以定位。

这里要强调的另一个重要点是:你需要知道每个输入文本使用哪种编码。不要猜测,不要假设。

输入 - 文件,文件句柄

[edit | edit source]

Perl 可以使用 PerlIO 层在数据进入 Perl 时自动对其进行解码

open (my $in_fh, "<:encoding(UTF-8)", $filename) || die;  # auto UTF-8 decoding on read

如果你已经有一个打开的文件句柄

binmode $in2_fh, ':encoding(UTF-8)';

不要使用:encoding(utf8)因为它不会检查你的传入文本是否为有效的 UTF-8,它只会将它标记为 UTF-8——参见Perlmonks.

如果你的文本文件包含一个字节顺序标记,请参见Perlmonks.

输入 - HTML 模板

[edit | edit source]

如果你使用 CGI 框架或模板引擎来拉取 UTF-8 编码的 HTML 模板文件,你可能需要告知它 UTF-8 编码,以便它在读取模板文件时对其进行“UTF-8 解码”。基本上,框架或模板引擎需要做我们在上一节中讨论的事情。

对于Template::Toolkit,如果你在模板文件中使用适当的字节顺序标记 (BOM) 来指示编码,则工具包将自动对其进行适当的解码。如果模板不使用 BOM,则使用 ENCODING 选项

my $template = Template->new({ ENCODING => 'utf8' });

HTML::Template 目前不支持对 UTF-8 编码的 HTML 模板文件进行解码。这是一个已知限制/错误。有一些解决方法

  • 一个补丁可用。
  • 你可以使用 TMPL_VARs 将 UTF-8 内容插入 N8CS(甚至 ASCII)编码的模板文件中。在将参数/内容插入 HTML 模板使用 TMPL_VARs 之前,对其进行 UTF-8 解码,隐式解码应该将生成的文本(,模板和填充的变量)在内部升级到 UTF-8。对于许多应用程序来说,这通常已经足够了。

输入 - 网页表单

[edit | edit source]

默认情况下,CGI.pm 不会解码您的表单参数。您可以使用-utf8pragma,它将把所有参数都当作 UTF-8 字符串处理(并解码),但这在您有任何二进制文件上传字段时会失败。一个更好的解决方案是覆盖 param 方法

package CGI::as_utf8;
BEGIN {
    use strict;
    use warnings;
    use CGI 3.47;  # earlier versions have a UTF-8 double-decoding bug
    {
        no warnings 'redefine';
        my $param_org = \&CGI::param;
        my $might_decode = sub {
            my $p = shift;
            # make sure upload() filehandles are not modified
            return $p if !$p || ( ref $p && fileno($p) );
            utf8::decode($p);  # may fail, but only logs an error
            $p
        };
        *CGI::param = sub {
            # setting a param goes through the original interface
            goto &$param_org if scalar @_ != 2;
            my ($q, $p) = @_;    # assume object calls always
            return wantarray
                ? map { $might_decode->($_) } $q->$param_org($p)
                : $might_decode->( $q->$param_org($p) );
        }
    }
}
1
---
use CGI::as_utf8;  # put this line in your app, e.g., in your CGI::Application module(s)

以上是 rhesa 的解决方案,稍微修改了一下——utf8::decode()用于代替 Encode'sdecode_utf8(),因为当只涉及 ASCII 字符时,它更有效(因为 UTF8 标志未设置)。请注意,该模块假定网页和表单始终使用 UTF-8 编码,并且始终使用 CGI.pm 的 OO 接口。

注意,浏览器应该使用与显示表单相同的字符编码来编码表单数据。因此,如果您发送 UTF-8 表单,则应为文本字段获得 UTF-8 编码的数据。您不应该使用accept-charset在您的 HTML 标记中。

输入 - STDIN

[edit | edit source]

当 Web 表单被 POST 时,表单数据通过 STDIN 传入 Perl。如果您使用的是 CGI.pm,文本表单数据可通过CGI.pm'sparam()方法获取,上一节介绍了如何正确处理 UTF-8 编码的文本表单数据。

如果您没有任何文件上传(即,您的所有数据都是文本),那么您可以使用 CGI::as_utf8 模块,而是将以下代码行添加到脚本的开头,以使在 STDIN 上接收的所有数据(即,所有 POST 的表单数据)自动解码为 UTF-8

binmode STDIN, ":encoding(UTF-8)";

不要使用

binmode STDIN, ":utf8";  # do NOT use this!

因为它不检查传入的文本是否为有效的 UTF-8,它只是将其标记为 UTF-8——参见 Perl 5 Wiki

上一节中的方法更可取,因为它在存在任何二进制表单数据(文件上传)时将“执行正确的事”。

如果您正在编写其他(非 CGI)程序来接收 STDIN 上的数据,请适当地解码

my $utf8_text    = decode('UTF-8',      readline STDIN);
my $iso8859_text = decode('ISO-8859-1', readline STDIN);
my $binary_data  = read(...);  # don't decode

注意decode()始终设置 Perl 的内部 UTF8 标志。

输入 - 数据库

[edit | edit source]

在“在所有地方使用 UTF-8”模型中,将您的数据库配置为以 UTF-8 存储值。

从 UTF-8 数据库读取数据时,确保传入的 UTF-8 编码字符串字段数据被 UTF-8 解码,但不要解码传入的二进制字段数据。

输入 - MySQL
[edit | edit source]

对于 MySQL,如果您使用的是 mysql_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 解码(和编码)是自动的。

use DBI();
my $dbh = DBI->connect('dbi:mysql:test_db', $username, $password,
                       {mysql_enable_utf8 => 1}
);

这意味着您不应该调用utf8::decode()(或任何其他 UTF-8 解码函数)在传入的字符串字段数据上——驱动程序会为您完成此操作。如果某个字段的传入数据只包含 ASCII 字节,则该字段的 UTF8 标志不会被设置(因此它似乎使用的是utf8::decode())。驱动程序也足够智能,不会解码二进制数据。

需要DBD::mysql的 4.004 或更高版本。UTF-8 最初在 MySQL v4.1 中可用。从 v5.0 开始,它是系统默认值。

输入 - PostgreSQL
[edit | edit source]

对于 PostgreSQL,从 DBD::Pg 3.0.0 版本开始,如果数据库也设置为 UTF-8,则字符串字段数据的 UTF-8 解码(和编码)是自动的。

对于之前的版本,您必须使用 pg_enable_utf8 数据库句柄属性,它将把所有非二进制数据设置为 UTF-8,而不管 client_encoding 值如何。

use DBI();
my $dbh = DBI->connect('dbi:Pg:test_db', $username, $password,
                       {pg_enable_utf8 => 1}
);

这意味着您不应该调用utf8::decode()(或任何其他 UTF-8 解码函数)在传入的字符串字段数据上——DBD::Pg 驱动程序会为您完成此操作。驱动程序也足够智能,不会解码二进制数据。

默认的 client_encoding 是使用数据库编码,因此如果您的数据库是 UTF-8,则它将默认设置。在其他情况下,您可能需要告诉 PostgreSQL 在从数据库发送数据时使用 UTF-8

SET CLIENT_ENCODING TO 'UTF8';

SET NAMES 'UTF8';

例如,使用 Rose::DB

__PACKAGE__->register_db(
   domain           => 'development',
...
   connect_options  => {
       pg_server_prepare => 0,
       pg_enable_utf8    => 1,
   },
   post_connect_sql => "SET CLIENT_ENCODING TO 'UTF8';",
);

参见 服务器和客户端之间的自动字符集转换

2. 处理字符串

[edit | edit source]

一旦所有传入的字符串都被内部解码为 UTF-8,您就可以像往常一样处理您的文本。正则表达式将起作用(如果使用 Perl v5.8 或更高版本)。

如果您在源代码中创建了包含非 ASCII 字符(高于0x7f)的字符串,请确保您将它们升级到内部 UTF-8 编码

my $text = "\xE0";  # 0xE0 = à in ISO-8859-1
utf8::upgrade($text);

my $unicode_char = "\x{00f1}";  # U+00F1 = ñ
utf8::upgrade($unicode_char);

Perl 5“Unicode 错误”

[edit | edit source]

(2011-05-03 更新:v5.14 现已可用,最终消除了 Unicode 错误。)

如果没有指定语言环境,如果您有字符在 0x80-0xFF(128-255)范围内的本地/N8CS 字符串,那么\d, \s, \w, \D, \S, \W(因此有正则表达式),以及lc(), uc()等等可能无法按预期工作,因为字符集的非 ASCII 部分(0x80-0xFF)对于这些操作将被忽略。(这是尝试在所有地方使用 UTF-8 的另一个原因。)如果没有语言环境,Perl 无法正确解释此范围内的字符,因为不同的编码在该范围内使用不同的字符,因此它会忽略它们——这被称为 *ASCII 语义*。

有三种方法可以避免这种“Unicode 错误”。最好的方法是升级到 Perl 5.14 并添加一个use 5.014;在文件顶部。其他两种方法涉及让本地编码的字符串切换到 UTF-8 编码——因为当内部编码为 UTF-8 时,将使用 *Unicode 语义*,它始终按预期工作。

1. 遵循“最佳实践”,始终正确解码所有外部输入文本/字节。在解码过程中,发现包含非 ASCII 字符的任何文本/字节将被转换为 UTF-8 内部编码。例如

use Encode;
# suppose $windows1252_octets contains text from an external input, and it contains the character
# "\xE0" (0xE0 = à). String $windows1252_octets will exhibit the Unicode bug -- it won't match /\w/
my $utf8_string = decode('cp1252',$windows1252_octets); # no Unicode bug, $utf8_string matches /\w/

2. 使用utf8::upgrade($native_string)强制 $native_string 切换到 UTF-8 内部编码。(即使字符串只包含 ASCII 字符,它仍然会被“升级”到 UTF-8。)

my $text = "\xE0";     # will exhibit Unicode bug, won't match /\w/
utf8::upgrade($text);  # no Unicode bug, matches /\w/

请注意,使用内部 UTF-8 编码,\w表示更大得多的字符集,因此正则表达式操作将变慢(与本地编码相比)。待办事项:实际性能下降是多少?*Unicode 语义* 的 \w 字符集是什么?

另请参见 Unicode::Semantics

2010-04-19 更新:v5.12 现已可用,并且“大小写转换组件”已修复:“Perl 5.12 现在捆绑了 Unicode 5.2。“feature”pragma 现在支持新的“unicode_strings”功能

 use feature "unicode_strings";

这将为字符串上的所有大小写转换操作启用 Unicode 语义,无论它们当前如何内部编码。”阅读 更多

3. 编码和输出

[edit | edit source]

Web 程序的输出包括 STDOUT(发送到您的浏览器以供 CGI 程序使用)、stderr(通常会进入 Web 服务器的错误日志)、数据库写入、日志文件输出等。

如果未对传出的文本进行编码,则文本将使用 Perl 内部格式的字节发送,这可能是本地/N8CS 和 UTF-8 的混合。这可能有效,但不要冒险——“最佳实践”要求明确地对所有输出进行适当编码。

如果您打印一个字符串,其中包含一个字符,其序数值大于 255,Perl 会向您发出警告

$ perl -e 'print "\x{0100}\n"'
Wide character in print at -e line 1.
Ā

要避免此警告,请明确编码输出(如下所述)。

输出 - STDOUT

[edit | edit source]

要确保发送回 Web 浏览器(即 STDOUT)的所有输出都使用 UTF8 编码,请将以下内容添加到 Perl 脚本的顶部附近

binmode STDOUT, ":encoding(utf8)";

如果您想要更高效一些(但没有遵循“最佳实践”),您可以选择仅在传出的页面被标记为 UTF-8 时对其进行编码

if(utf8::is_utf8($page)) {
   utf8::encode($page);
}
# else, $page is natively encoded, so skip encoding for output

这是一个 片段,可与 CGI::Application 框架一起使用

__PACKAGE__->add_callback('postrun', sub {
   my $self = shift;
   # Make sure the output is utf8 encoded if it needs it
   if($_[0] && ${$_[0]} && utf8::is_utf8(${$_[0]}) ){
       utf8::encode( ${$_[0]} );
       # ${$_[0]} .= 'utf8::encode() called';  # useful for debugging
   }
});

以上代码应放在 CGI::Application 基类中。可选地,该代码可以添加到 cgiapp_postrun() 中。

请注意,如果所有输入 UTF-8 字节都被正确解码,那么所有上述编码技术才能正常工作。

输出 - 数据库

[edit | edit source]

如上所述,在“在所有地方使用 UTF-8”模型中,将您的数据库配置为以 UTF-8 存储值。

将数据写入 UTF-8 数据库(INSERT、UPDATE 等)时,确保您的 UTF-8 字符串在写入数据库之前被 UTF-8 编码。不要编码二进制字段数据。

输出 - MySQL
[edit | edit source]

如上所述,如果您使用 mysql_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 编码(和解码)将自动进行。这意味着您在使用此属性时不应调用utf8::encode()(或任何其他 UTF-8 编码函数) — 驱动程序会为您执行此操作。驱动程序也很聪明,不会对二进制数据进行编码。

需要 DBD::mysql 的 4.004 或更高版本。UTF-8 最初在 MySQL v4.1 中可用。从 v5.0 开始,它是系统默认值。

输出 - PostgreSQL
[编辑 | 编辑源代码]

如上所述,如果您使用 pg_enable_utf8 数据库句柄属性,字符串字段数据的 UTF-8 编码(和解码)将自动进行。这意味着您在使用此属性时不应调用utf8::encode()(或任何其他 UTF-8 编码函数) — DBD::Pg 驱动程序会为您执行此操作。驱动程序也很聪明,不会对二进制数据进行编码。

您可能还需要告诉 PostgreSQL 预期传入数据库的 UTF-8(待定:何时?)。

SET CLIENT_ENCODING TO 'UTF8';

SET NAMES 'UTF8';

参见 服务器和客户端之间的自动字符集转换

输出 - 文件、文件句柄

[编辑 | 编辑源代码]

如果您需要写入文件,Perl 可以使用PerlIO

open my $out_fh, ">:utf8", $filename  or die;  # auto UTF-8 encoding on write

如果你已经有一个打开的文件句柄

binmode $out2_fh, ':utf8';

告诉浏览器使用 UTF-8

[编辑 | 编辑源代码]

要向浏览器提供 UTF-8 编码的页面,“最佳做法”是在 HTTP Content-Type 标头中指定 UTF-8 字符集,并在 HTML 文件中的 content-type <meta> 标记中指定 UTF-8 字符集。CGI.pm 默认发送以下 Content-Type 标头

Content-Type: text/html; charset=ISO-8859-1

添加以下内容以使 UTF-8 而不是 ISO-8859-1 被使用,其中 $q 是您的 CGI 对象

$q->charset('UTF-8');

如果您使用 CGI::Application 框架,请将上述行放在 cgiapp_init() 中。

如果您没有使用 CGI.pm 生成 HTML 标记,请将以下 meta 标记作为 HTML 标记 <header> 部分中的第一个 meta 标记

<meta http-equiv="content-type" content="text/html; charset=UTF-8" />

Perl 源代码

[编辑 | 编辑源代码]

如果您只需要在源代码中的几个字符串中嵌入几个 Unicode 字符,则无需以 UTF-8 格式保存源代码/文件。相反,使用\x{...}chr()在您的代码中

  my $smiley = "\x{263a}";
  or
  my $smiley = chr(0x263a);

如果您有很多 Unicode 字符,或者您更喜欢以 UTF-8 格式保存源代码,那么您需要告诉 Perl 您的源代码是以 UTF-8 格式编码的。为此,请将以下行添加到您的源代码中

 use utf8;  # this script is in UTF-8

这是您的程序应该永远拥有上述行的唯一原因 — 请参阅 utf8

如果您的源代码是以 UTF-8 格式编码的,请确保您的编辑器支持以 UTF-8 格式读取、编辑和写入!

注意事项

[编辑 | 编辑源代码]

通常,您可能不会注意到 Unicode 问题,直到使用代码点大于 128 的字符。这是因为 ASCII、ISO-8859-1、Windows-1252 和 UTF-8 都对前 128 个 Unicode 代码点使用相同的单字节值进行编码。要对您的应用程序进行良好的 Unicode 测试,请尝试使用0x80 - 0x9F(128-159)范围内的字符,以及大于0xFF (255).

print 中的宽字符位于…

[编辑 | 编辑源代码]

如果您打印包含序数值大于 255 的字符的字符串,Perl 将向您发出警告(因此它是一个“宽”字符,需要多于一个字节的存储空间)

print 中的宽字符位于…第…行

显式地对您的输出进行编码以避免此警告。

无法解码包含宽字符的字符串,位于…

[编辑 | 编辑源代码]

如果您收到此错误,您的代码可能正在尝试第二次解码同一个字符串,这将失败。

Web 服务器始终发送 ISO-8859-1 标头

[编辑 | 编辑源代码]

如果您遵循了上述步骤,但您的页面显示不正常,可能是您的 Web 服务器配置为始终在标头中发送特定字符编码,例如 ISO-8859-1。要确定 Web 服务器是否正在发送 content-type 标头

$ lwp-request -de www.bing.com | grep Content

Apache 可能配置了以下内容

AddDefaultCharset ISO-8859-1

如果可以,请删除该行,或将其更改为

AddDefaultCharset UTF-8

如果服务器提供的页面都使用 UTF-8。另请参阅 当 Apache 和 UTF-8 发生冲突时

ISO-8859-1 与 Windows-1252

[编辑 | 编辑源代码]

由于您正在学习字符编码,因此您需要了解国际 ISO-8859-1 和 Microsoft 专有 Windows-1252 之间的区别。来自 Windows-1252

[Windows-1252] 在可打印字符方面是 ISO 8859-1 的超集,但它与 IANA 的 ISO-8859-1 不同,因为在 80 到 9F(十六进制)范围内它使用的是可显示字符,而不是控制字符。[…] 通常将 Windows-1252 文本错误地标记为字符集标签 ISO-8859-1。[…] 大多数现代 Web 浏览器和电子邮件客户端将媒体类型字符集 ISO-8859-1 视为 Windows-1252 以适应这种错误标记。这现在是 HTML5 规范中的标准行为,该规范要求以 ISO-8859-1 为广告的文档实际上使用 Windows-1252 编码进行解析。

以下是一个有趣的程序可以尝试

my @undefined_chars_in_windows_1252 = (0x81, 0x8d, 0x8f, 0x90, 0x9d);
my %h = map { $_ => undef } @undefined_chars_in_windows_1252;
foreach my $i (0x80 .. 0x9f) {
	next if exists $h{$i};
	printf "%02x:%c ", $i,$i;
}

您看到了什么?您看到了 Windows-1252 字符,没有字符,还是方框?如果您使用的是 PuTTY,请更改设置...窗口,翻译,然后尝试选择 ISO-8859-1 或 Windows-1252 并再次运行该程序。

Microsoft“智能”引号

[编辑 | 编辑源代码]

Microsoft Word 使用那些漂亮的左和右奇特/智能引号。如果您将这些字符复制粘贴到使用 Windows-1252 字符集(或可能甚至 ISO-8859-1 字符集)提供的 Web 表单中,这些字符可能会使用模糊的0x80-0x9F(128-159)范围提交到 Web 服务器。(回想一下,Unicode 在此范围内定义了控制字符 — 而不是像智能引号这样的可打印字符。)如果您的 Perl 脚本没有正确解码提交的表单(即,根据 Web 表单使用的相同字符编码),您将得到乱码。

正确地解码和编码,您就不会遇到 Microsoft 智能引号或模糊范围内的任何其他字符的任何问题。更好的是,如果您将所有 Web 页面提供为 UTF-8,提交的表单不应该包含这些模糊值,因为“粘贴”操作应该自动将这些字符转换为有效的 Unicode 字符。然后,您的 Perl 脚本将只接收有效的 UTF-8 编码字符。

浏览器中的奇怪字符

[编辑 | 编辑源代码]

奇怪的字符:

这是 Unicode 的“替换字符”(代码点 U+FFFD),用于指示 Unicode 解析器(如浏览器)何时无法解码 Unicode 编码数据的流。问题可能是链中某处的编码/解码问题。(U+FFFD 在 UTF-8 中编码为 EF BF BD。如果您保存 Web 页面,然后在 bvi 中打开它,您可能会看到 EF BF BD。)IE 将替换字符显示为空方框。Firefox 使用带有问号的黑色菱形。

通常,这些替换字符出现是因为 HTML 数据是 Windows-1252 编码的,但浏览器被指示使用 UTF-8 编码。在您的浏览器中,选择查看->字符编码,看看是否设置为 UTF-8。如果是,尝试选择Windows-1252西欧 (Windows),看看是否解决了问题。如果解决了,那么您就知道 Web 服务器正在提供错误的字符编码——发送的内容(即数据如何编码)与浏览器被告知使用的字符集(即 HTTP 标头和/或元标记)之间存在不匹配。如果无法解决问题,可能是您的计算机上没有安装 Unicode 字体,或者 Unicode 字体没有该特定字符的字形。

奇怪的字符: ‘ ’ “ †• – —

这些是与以下 Windows-1252 字符的多字节 UTF-8 编码相对应的单个字符

‘ ’ “ ” • – —

它们在模糊的 0x80-0x9F (128-159) 范围内。通常,这些字符出现是因为 HTML 数据是 UTF-8 编码的,但浏览器被指示使用 ISO-8859-1 或 Windows-1252。在您的浏览器中,尝试将编码更改为UTF-8,看看是否解决了问题。如果无法解决问题,或者编码已经设置为 UTF-8,则可能在某个地方存在双重编码问题。

奇怪的字符: ‘ ’ “ †• – —

这些也对应于模糊的 0x80-0x9F (128-159) 范围内的某些字符。如果您看到上述序列,则可能是您忘记在您的 Perl 程序中解码传入的 UTF-8 数据(例如从 UTF-8 编码的 HTML 表单提交的表单数据),然后您将它 UTF-8 编码以进行输出——本机编码的字符串被 UTF-8 编码(不好)。通过调用以下方法来解决问题utf8::decode()在传入的 UTF-8 编码数据上。

编辑器中的奇怪字符

[编辑 | 编辑源代码]
  • 确保您的编辑器支持以 UTF-8 格式读取、编辑和写入
  • 确保您将编辑器设置为使用 Unicode 字体
  • 确保您已安装 Unicode 字体

在 Windows 上安装 Unicode 字体

[编辑 | 编辑源代码]

如果您有 此页面 上列出的其中一个 Microsoft 产品,您应该拥有Arial Unicode MS字体。如果未安装,请按照以下步骤安装:添加/删除程序,选择 MS-Office,添加或删除功能,单击“选择高级”,Office 共享功能,国际支持,通用字体。应用更改并重新启动您的 Web 浏览器。

我要求 UTF-8,但我得到了一些其他东西!?

[编辑 | 编辑源代码]

如果您专门要求 UTF-8 文本,但您收到的八位字节流不是有效的 UTF-8 编码,在很多情况下,您可能可以假设传入的文本/八位字节是 ISO-8859-1/Latin-1 或 Windows-1252。使用 Windows-1252 解码,因为它是一个 ISO-8859-1 的超集。

双重编码

[编辑 | 编辑源代码]

如果您不解码 UTF-8 文本/八位字节,Perl 将假设它们使用 N8CS(通常是 ISO-8859-1/Latin-1)编码。这意味着多字节 UTF-8 字符的单个八位字节被视为单独的字符(不好)。如果这些单独的字符后来被编码为 UTF-8 以进行输出,则会导致“双重编码”。这类似于 HTML 双重编码——例如,&amp;gt; 而不是 &gt;。

自动字体替换

[编辑 | 编辑源代码]

大多数现代浏览器和文字处理器执行 字体替换,这意味着如果一个字符不在当前字体中,应用程序将搜索您所有的字体,直到找到包含该字符的字体,然后它将使用该字体中的字形显示该字符。

有时 IE7 和 IE8 似乎无法正确执行字体替换。一种解决方法是在 CSS 中将 Unicode 字体指定为第一个字体font-family属性。IE6 不被视为现代浏览器,它不会执行字体替换。

创建 Unicode 字符

[编辑 | 编辑源代码]

在 Windows 上,您始终可以使用字符映射应用程序来选择、复制和(切换到您的应用程序,然后)粘贴 Unicode 字符。确保“字符集”下拉框设置为“Unicode”。您也可以使用该应用程序查看字体、字符以及每个字符的 Unicode 代码点值。

在 Perl 中

[编辑 | 编辑源代码]
  my $utf8_char  =  "\x{263a}";    # for codepoints above 0xFF
  $utf8_char     =~ /\x{263a}/;    # same syntax for regex
  my $cloud_char =  chr(0x2601);   # run-time, ord() does the reverse

如果您的 Perl 源代码文件是 UTF-8 格式的,您可以直接输入 Unicode 字符

  use utf8;                # tells Perl this file is UTF-8 encoded
  my $utf8_char  =  "☺";   # U+263a, "White Smiling Face"

在 Web 表单中

[编辑 | 编辑源代码]

在 Windows 上

  • 要从 Windows-1252 代码页 插入字符:打开数字锁定键,按住 Alt,然后使用数字键盘,输入 0 后面跟着您想要的字符的十进制值。
  • 要从当前 DOS 代码页(通常是 CP-437)插入字符:按照上述步骤操作,但不要输入初始的 0。

但是等等,我们想要插入的是 Unicode 字符,而不是 Windows-1252 或 CP-437 字符!好吧,如果应用程序期望 UTF-8,Windows 将为我们将其转换为 Unicode/UTF-8。

在 Web 表单(文本框或文本区域)中,输入 Alt-0147 以从 Windows-1252 字符集生成那些讨厌的智能引号之一。如果网页的字符编码设置为 UTF-8,Windows 应该将 147 字符转换为相应的 UTF-8 编码。(在内部,Windows 可能将 0147 转换为 UTF-16,然后将其转换为应用程序正在使用的字符集。在这种情况下,字符集是 Unicode,而 Windows-1252 字符 147 被转换为其 Unicode 代码点等效值,U+201C。)提交表单时,该字符应作为三个八位字节发送到 Web 服务器:E2 80 9C——这就是 U+201C 使用 UTF-8 编码时的样子。

如果网页的字符编码设置为 Windows-1252,则该字符应作为单个八位字节发送:0x93(即十进制 147)。如果网页的字符编码设置为 ISO-8859-1,则该字符也将作为单个八位字节发送,但其值可以是 0x93 或 0x22(0x22 是 ASCII 和 ISO-8859-1 引号字符)。如果浏览器在指定 ISO-8859-1 时使用超集 Windows-1252 编码,则会发送 0x93。否则,该字符将转换为 ISO-8859-1 中正式定义的唯一引号字符,0x22。

希望您能明白为什么必须知道用于传入表单/文本的编码方式,以便能够在您的 Perl 程序中对其进行正确解码(作为 UTF-8 或 Windows-1252)。

另请参见 如何输入… - Yahoo Answers。

UTF-8 与 utf8

[编辑 | 编辑源代码]

从 Perl 5.8.7 开始,UTF-8 是严格的、正式的 UTF-8。如果您尝试编码或解码无效的 UTF-8,例如,Encode 模块会抱怨。

encode("UTF-8", "\x{FFFF_FFFF}", 1);  # croaks

相比之下,utf8 是宽松的、松散的版本,允许几乎任何 4 字节值

encode("utf8", "\x{FFFF_FFFF}", 1);   # okay
encode_utf8("\x{FFFF_FFFF}", 1);      # okay

Encode 从 2.10 版开始知道区别。

utf8::encode()utf8::decode()使用正式的 UTF-8。

Encode 模块与内置/核心 utf8:

[编辑 | 编辑源代码]

要解码和编码 UTF-8,您可以使用 Encode 模块或 Perl 核心在 utf8:: 包中定义的函数。Encode 模块更灵活,允许不同的方法来处理格式错误的数据。但是,utf8:: 包可以执行一些不同的技巧。

您应该注意 Encode 模块中的一个错误:每当使用 Encode 模块解码文本时,UTF8 标志总是被打开。文档会让您相信,如果文本只包含 ASCII 字符并且您正在解码 UTF-8,则 UTF8 标志是关闭的。事实并非如此——该标志始终处于打开状态,如下表所示。

如果可以在解码后关闭 UTF8 标志,则可以提高性能(如果文本只包含 ASCII 八位字节,则可以这样做)。使用utf8::decode()为了达到这种效率,因为如果八位字节序列只包含 ASCII 八位字节,它不会打开标志。(这是我通常使用的解码函数。)

下面,请查看 Encode 文档,了解 CHECK 选项,这些选项与模块处理畸形数据的机制相关。

UTF-8 函数
函数 UTF8 标志 描述 / 备注
$flag = utf8::is_utf8($string); N/A 测试 $string 是否以 UTF-8 编码。如果否,返回 false;否则返回 true
$flag = utf8::decode($utf8_octets); 依赖 尝试就地将 UTF-8 八位字节序列转换为相应的 N8CS 或 UTF-8 字符串,具体取决于情况。如果$utf8_octets包含非 ASCII 八位字节(即多字节 UTF-8 编码字符),则 UTF8 标志将被打开,结果字符串为 UTF-8。否则,UTF8 标志将保持关闭状态,结果字符串为 N8CS。这是唯一可能导致 N8CS 字节字符串的解码函数。如果返回 false$utf8_string未正确编码为 UTF-8;否则返回 true
$utf8_string = decode('UTF-8', $utf8_octets [, CHECK]) 打开 将 UTF-8 八位字节序列解码为 UTF-8 字符串。遵循严格的官方 UTF-8 解码规则(有关讨论,请参见上一节)。
$utf8_string = decode('utf8', $utf8_octets [, CHECK]) 打开 将 UTF-8 八位字节序列解码为 UTF-8 字符串。遵循宽松的解码规则(有关讨论,请参见上一节)。
$utf8_string = decode_utf8($utf8_octets [, CHECK]) 打开 将 UTF-8 八位字节序列解码为 UTF-8 字符串。等效于decode("utf8", $utf8_octets),因此使用宽松解码。
$octet_count = utf8::upgrade($n8cs_string); 打开 就地将 N8CS 字节字符串转换为相应的 UTF-8 字符串。返回现在用于以 UTF-8 形式内部表示字符串的八位字节数。此函数应用于将 N8CS 字节字符串中包含的字符转换为 UTF-8,从而避免 Perl 5“Unicode 错误”0x80-0xFFrange to UTF-8, thereby avoiding the Perl 5 "Unicode Bug".
utf8::encode($string) 关闭 就地将 N8CS 或 UTF-8 $string 转换为 UTF-8 八位字节序列。
$utf8_octets = encode('UTF-8', $string [, CHECK]) 关闭 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。遵循严格的官方 UTF-8 编码规则(有关讨论,请参见上一节)。
$utf8_octets = encode('utf8', $string) 关闭 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。遵循宽松的 UTF-8 编码规则(有关讨论,请参见上一节)。由于所有可能的字符都有宽松的 utf8 表示形式,因此此函数不会失败。
$utf8_octets = encode_utf8($string) 关闭 将 N8CS 或 UTF-8$string编码为 UTF-8 八位字节序列。等效于encode("utf8", $string),因此使用宽松编码。由于所有可能的字符都有宽松的 utf8 表示形式,因此此函数不会失败。
$flag = utf8::downgrade($utf8_string [, FAIL_OK]); 关闭 就地将 UTF-8 字符串转换为等效的 N8CS 字节字符串。如果失败,则$utf8_string不能用 N8CS 编码表示。如果 FAIL_OK 为真,则在失败时会死亡,否则返回 false。成功时返回 true

Perl 字符编码

[edit | edit source]

要确定 Perl 支持哪些字符编码

perl -MEncode -le "print for Encode->encodings(':all')"

重要的是要记住,Perl 内部只使用两种字符编码:本机/字节和 UTF-8/字符。任何使用除 N8CS 之外的编码(平台的本机 8 位字符集,通常为 ISO-8859-1/Latin-1)的字符都必须在进入 Perl 时解码。

网站“x”使用什么?

[edit | edit source]

查看页面,然后在浏览器中,查看->字符编码以查看选择了哪种编码。另外,查看 HTML 源代码,看看是否存在元标签

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

您还可以使用以下方法查看返回了哪个 Content-Type 标头

$ lwp-request -de www.bing.com | grep Content

此维基使用 UTF-8。

HTML 字符实体

[edit | edit source]

在您的 UTF-8 旅程中,您可能会遇到 HTML 字符实体。从 HTML 4.0 开始,支持 252 个 字符实体。每个实体都有一个 Unicode 代码点和一个实体名称。可以在 HTML 标记中使用它们中的任何一个。例如,注册符号可以在 HTML 中表示为&#174;&reg;

许多字体支持此字符集,如果该字符集足够满足您的应用程序需求,则可能不需要 UTF-8,但您的应用程序需要在需要特殊字符的地方使用 HTML 编码。

操作系统和 Unicode

[edit | edit source]

有趣的是,流行的操作系统使用哪种 Unicode 编码。从 维基百科 中可以看出:“Windows NT(及其后代,Windows 2000、Windows XP、Windows Vista 和 Windows 7)使用 UTF-16 作为唯一的内部字符编码。Java 和 .NET 字节码环境、Mac OS X 和 KDE 也将其用于内部表示。UTF-8 已成为大多数类 Unix 操作系统的主要存储编码(尽管某些库也使用其他编码),因为它可以相对轻松地替换传统的扩展 ASCII 字符集。”

参考资料

[edit | edit source]

脚注

[edit | edit source]

^ - N8CS 是为本文档创造的术语。不要指望在其他地方看到这个术语。


前一个:PSGI 索引 下一个:Perl 6
华夏公益教科书