如何让 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::encode
、utf8::decode
、utf8::upgrade
和utf8::downgrade
. (OTOH,不要使用Encode::_utf8_on
、Encode::_utf8_off
、Encode::is_utf8
、utf8::is_utf8
和 utf8::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 响应