在Perl中如何找到Unicode字符串的长度?

18
perldoc页面上的length()告诉我,如果要在字节中查找Unicode字符串,应该使用bytes::length(EXPR),并且bytes页面也有相同的说明。
use bytes;
$ascii = 'Lorem ipsum dolor sit amet';
$unicode = 'Lørëm ípsüm dölör sît åmét';

print "ASCII: " . length($ascii) . "\n";
print "ASCII bytes: " . bytes::length($ascii) . "\n";
print "Unicode: " . length($unicode) . "\n";
print "Unicode bytes: " . bytes::length($unicode) . "\n";

然而,这个脚本的输出与man手册不一致:
ASCII: 26
ASCII bytes: 26
Unicode: 35
Unicode bytes: 35

我认为length()和bytes::length()对于ASCII和Unicode字符串返回的结果相同。 我的编辑器默认设置为UTF-8编写文件,因此我认为Perl将整个脚本解释为Unicode - 这是否意味着length()会自动正确处理Unicode字符串? 编辑:请看我的评论;我的问题并不十分合理,因为在上面的示例中,length()没有正常工作 - 它显示Unicode字符串的字节数而不是字符数。我最初遇到这个问题是因为我需要在HTTP消息中设置Content-Length头(以字节为单位)。我已经阅读了有关Perl中的Unicode的内容,并期望必须进行一些繁琐的操作才能使事情正常运行,但是当length()恰好返回我所需的内容时,我感到困惑!请参见接受的答案,了解Perl中的use utf8use bytesno bytes的概述。

1
我不明白你为什么说 length() 正确处理 Unicode 字符串。在你的示例中,length() 给出与 bytes::length() 相同的结果,即字节数而不是字符数(正确的应该是字符数)。 - Inshallah
1
换句话说,length($unicode)将字符串解释为ASCII而不是Unicode。 - Inshallah
你完全正确!我完全忽略了这一点——在我的程序中,我正在使用length()函数来设置HTTP消息中的Content-Length头部,这需要以字节为单位。阅读了length()函数的文档后,我原本期望该函数返回不正确的内容,但是实际上当Perl处于"use bytes"模式时,它确切地返回了我想要的内容:Unicode字符串的字节数而非字符数。 - Drew Stephens
你为什么想要一个Unicode字符串的长度?你用它做什么? - brian d foy
4个回答

27
如果您的脚本编码为UTF-8,则请使用utf8 pragma。另一方面,bytes pragma将强制字节语义的长度,即使字符串是UTF-8。两者都在当前词法作用域中工作。
$ascii = 'Lorem ipsum dolor sit amet';
{
    use utf8;
    $unicode = 'Lørëm ípsüm dölör sît åmét';
}
$not_unicode = 'Lørëm ípsüm dölör sît åmét';

no bytes; # default, can be omitted
print "Character semantics:\n";

print "ASCII: ", length($ascii), "\n";
print "Unicode: ", length($unicode), "\n";
print "Not-Unicode: ", length($not_unicode), "\n";

print "----\n";

use bytes;
print "Byte semantics:\n";

print "ASCII: ", length($ascii), "\n";
print "Unicode: ", length($unicode), "\n";
print "Not-Unicode: ", length($not_unicode), "\n";

这将输出:
Character semantics:
ASCII: 26
Unicode: 26
Not-Unicode: 35
----
Byte semantics:
ASCII: 26
Unicode: 35
Not-Unicode: 35

5

bytes 语法的目的是替换当前作用域中的 length 函数(以及其他几个与字符串相关的函数)。因此,程序中每次调用 length 函数都是调用 bytes 提供的 length 函数。这更符合您的意图:

#!/usr/bin/perl

use strict;
use warnings;

sub bytes($) {
    use bytes;
    return length shift;
}

my $ascii = "foo"; #really UTF-8, but everything is in the ASCII range
my $utf8  = "\x{24d5}\x{24de}\x{24de}";

print "[$ascii] characters: ", length $ascii, "\n",
    "[$ascii] bytes     : ", bytes $ascii, "\n",
    "[$utf8] characters: ", length $utf8, "\n",
    "[$utf8] bytes     : ", bytes $utf8, "\n";

你的推理中还存在一个微妙的错误,那就是有一种叫做Unicode字节的东西。Unicode是字符的枚举。例如,它表示U+24d5是&#x24d5(圆圈内的小写拉丁字母f);但Unicode没有说明一个字符占用多少字节,这留给编码来确定。UTF-8表示它占用3个字节,UTF-16表示它占用2个字节,UTF-32表示它占用4个字节等。这里比较Unicode编码。Perl默认使用UTF-8作为其字符串编码。UTF-8的好处是前127个字符与ASCII完全相同。

2
我发现可以使用 Encode 模块来影响长度的计算方式。
如果 $string 是 utf8 编码的字符串。
Encode::_utf8_on($string); # 此后,长度函数将显示代码点数。
Encode::_utf8_off($string); # 此后,长度函数将显示字符串中的字节数。

-1

这里有一些问题评论。

Perl 不知道——也不关心哪些字符串是“Unicode”,哪些不是。它所知道的只是组成字符串的代码点。

窥视 Perl 的内部 UTF8 标志表明,您可能对 Perl 字符串有错误的理解。例如,“UTF-8 编码的字符串”——即 utf8::encode 等编码操作的结果——通常不会设置该标志。

在某些接口中,这种抽象泄漏了,具有内部 UTF8 标志集合的字符串与没有该标志的相同代码点集合(即在 utf8::downgrade 之后)行为不同。依赖这些行为是不明智的,因为 Perl 自己的维护者将其视为错误。大多数问题都可以通过“unicode_strings”和“unicode_eval”功能来解决,其余问题可以通过来自 CPAN 的 Sys::Binmode 来解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接