如何让 Perl 检测错误的 UTF-8 序列?

Posted

技术标签:

【中文标题】如何让 Perl 检测错误的 UTF-8 序列?【英文标题】:How can I get Perl to detect bad UTF-8 sequences? 【发布时间】:2011-02-09 00:40:53 【问题描述】:

我正在运行 Perl 5.10.0 和 Postgres 8.4.3,并将字符串写入数据库,该数据库位于 DBIx::Class 后面。

这些字符串应该是 UTF-8,因此我的数据库是以 UTF-8 运行的。不幸的是,其中一些字符串很糟糕,包含格式错误的 UTF-8,所以当我运行它时,我遇到了异常

DBI Exception: DBD::Pg::st execute failed: ERROR: invalid byte sequence for encoding "UTF8": 0xb5

我认为我可以简单地忽略无效的标题,然后担心格式错误的 UTF-8,因此使用此代码,它应该标记并忽略错误的标题。

if(not utf8::valid($title))
   $title="Invalid UTF-8";

$data->title($title);
$data->update();

然而 Perl 似乎认为字符串是有效的,但它仍然抛出异常。

如何让 Perl 检测错误的 UTF-8?

【问题讨论】:

这是题外话,但是您使用 5.10.0 而不是 5.10.1 有什么特别的原因吗? 5.10.1 中有一些很好的兼容性修复。 【参考方案1】:

首先,请遵循文档 - utf8 模块应该在“使用 utf8;”中使用表格以表明您的源代码是 UTF-8 而不是 Latin-1。不要使用任何 utf8 函数。

Perl 区分字节和 UTF-8 字符串。在字节模式下,Perl 不知道也不关心您使用的是什么编码,如果您打印它,它将使用 Latin-1。以欧元符号 (€) 为例。在 UTF-8 中,这是 3 个字节,0xE2、0x82、0xAC。如果打印这些字节的长度,Perl 将返回 3。同样,它不关心编码。它可以是任何字节或任何编码,合法或非法。

如果您使用Encode 模块并调用Encode::decode("UTF-8', $bytes),您将获得一个设置了所谓的UTF8 标志的新字符串。 Perl 现在知道你的字符串是 UTF-8 格式,并且会返回长度 1。

utf8::valid 的问题只适用于第二种字符串。您的字符串可能是第一种形式,字节模式,utf8::valid 只为字节形式的任何内容返回 true。这记录在 perldoc 中。

解决方案是让 Perl 将您的字节字符串解码为 UTF-8,并检测任何错误。正如 brian d foy 解释的那样,这可以使用 FB_CROAK 来完成:

my $ustring =
    eval  decode( 'UTF-8', $byte_string, FB_CROAK ) 
    or die "Could not decode string: $@";

然后您可以捕获该错误并跳过那些无效字符串。

或者,如果您知道您的代码大部分是 UTF-8,并且到处都有一些无效序列,您可以使用:

my $ustring = decode( 'UTF-8', $byte_string );

使用默认模式FB_DEFAULT,将无效字符替换为U+FFFD,即Unicode REPLACEMENT CHARACTER(带问号的菱形)。

在大多数情况下,您可以将字符串直接传递给您的数据库驱动程序。某些驱动程序可能会要求您先将字符串重新编码回字节形式:

my $byte_string = encode('UTF-8', $ustring);

在调用decode 之前,您还可以使用在线正则表达式来检查有效的 UTF-8 序列(查看其他 Stack Overflow 答案)。如果您使用这些正则表达式,则无需进行任何编码或解码。

最后,请在致电decode 时使用UTF-8 而不是utf8。后者更加宽松,允许一些无效的 UTF-8 序列(如 Unicode 范围之外的序列)通过。

【讨论】:

Encode::encode 在这里是错误的方法,encode 用于从 UTF-8-Perlstrings 中获取指定字符集中的字节,但如果 is_utf8 失败,则没有这样的字符串并且 encode 会产生垃圾。真正想要做的是使用 Encode::decode 首先从用于 $string 的任何字符集中获取 UTF-8-Perlstring。如果不知道这是哪个字符集,则需要猜测或失败或其他任何内容,但肯定不要编码为其他任何内容,因为这只会让生活变得更糟。 FB_CROAK 是你的朋友! :-) is_utf8() 根本没有检测到有效的 UTF8——它只是检测 perl 的内部 UTF8 标志是否打开。使用无效数据很容易打开该标志。 @ThorstenSchöning 感谢您的评论,我已经更新了答案,希望它现在能反映现实:) Re "不要使用任何 utf8 函数。",没有理由不使用utf8::encodeutf8::decodeutf8::upgradeutf8::downgrade . (OTOH,不要使用Encode::_utf8_onEncode::_utf8_offEncode::is_utf8utf8::is_utf8utf8::valid 我收到了Bareword "FB_CROAK" not allowed while "strict subs" in use ...。通过将FB_CROAK 替换为Encode::FB_CROAK 来修复。【参考方案2】:

你是如何得到你的字符串的?你确定 Perl 认为它们已经是 UTF-8 了吗?如果它们还没有被解码(也就是说,八位字节被解释为某种编码),你需要自己做:

    use Encode;

    my $ustring =
      eval  decode( 'utf8', $byte_string, FB_CROAK ) 
      or die "Could not decode string: $@";

更好的是,如果您知道您的字符串源已经是 UTF-8,您需要将该源读取为 UTF-8。查看获取字符串的代码,看看是否正确。

【讨论】:

eval返回一个真值以避免误识别空字符串。 或者我可以使用 // 而不是 or 来测试定义性。【参考方案3】:

正如utf8::valid 的文档所指出的,如果字符串被标记为 UTF-8 并且它是有效的 UTF-8,则返回 true,或者如果字符串根本不是 UTF-8。尽管如果不查看上下文中的代码并知道数据是什么就无法判断,但很可能您想要的根本不是“有效的 utf8”检查;可能你只需要做

$data->title( Encode::encode("UTF-8", $title) )

【讨论】:

Encode::encode 在非 UTF-8-Perlstrings 上只会让事情变得更糟,不要那样做。使用 decode(!) 或处理您有一些应该是字符串的字节,但您不再知道它们的字符集的事实。

以上是关于如何让 Perl 检测错误的 UTF-8 序列?的主要内容,如果未能解决你的问题,请参考以下文章

关于 unpack() 和 printf() 中的 v 标志的 Perl 问题

如何使用 perl 和 Net::OpenSSH 检测远程端是不是只处理协议 1?

Perl LWP::UserAgent 错误处理 UTF-8 响应

如果子发出信号,Perl 从 fork/exec 中检测到错误的退出代码

如何在 Perl 中将命令行参数视为 UTF-8?

如何从 Perl 输出 UTF-8?