utf-8的perl binmode仅用于\ x {codepoint},而不是用于三字节编码的\ x编码

时间:2016-07-11 08:29:05

标签: perl unicode encoding utf-8

Euro character是UTF-8中的0xe282ac

我正在尝试在perl中使用字符串,并将UTF-8字符输出到STDOUT。

所以我将脚本设置为带有'use utf8;'

的UTF-8

我将STDOUT设置为带有'binmode'的UTF-8。

示例脚本是:

use utf8;
binmode STDOUT, ':utf8';
print "I owe you 160\x{20ac}\n";
print "I owe you 80\xe2\x82\xac\n";  # UTF-8 encoding?

\ x {codepoint}工作正常,但编码UTF-8会给我一个错误:

I owe you 160€
I owe you 80â¬

3 个答案:

答案 0 :(得分:5)

如果你想要一个由三个字节state组成的字符串,你可以这样声明:

E2 82 AC

双引号字符串中的my $bytes = "\xE2\x82\xAC"; 形式使用两个十六进制数字(总是两个)来表示一个字节。

上面的字符串包含3个字节。如果我们将字符串传递给\xXX函数,它将返回3:

length

Perl无法知道这三个字节是否用于表示欧元符号。它们同样可以是JPEG文件内部的三字节序列,也可以是ZIP文件,也可以是遍历网络的SSL编码的TCP数据流。 Perl不知道或不关心 - 它只是三个字节。

如果你真的想要一个字符串(而不是字节),那么你需要以允许Perl使用其Unicode字符的内部表示将它们存储在内存中的方式提供字符数据。一种方法是在源代码中以UTF8格式提供非ASCII字符。如果你这样做,你需要在脚本顶部说say 'Length of $bytes is: ' . length($bytes); # 3 告诉Perl解释器将非ASCII字符串文字视为utf8:

use utf8

或者,您可以使用带有1-5个十六进制字符的表单\ x {X ...}来表示Unicode代码点编号。这将声明一个相同的字符串:

use utf8;

my $euro_1 = "€";

这些字符串中的每一个都包含Perl内部编码中的欧元字符的多字节表示。 Perl知道字符串是字符串,因此my $euro_2 = "\x{20ac}"; 函数在每种情况下都会返回1(对于1个字符):

length

Perl内部字符串表示的定义特征是它用于 in Perl。如果要将数据写入文件或套接字,则需要将字符串编码为字节序列:

say 'Length of $euro_1 is: ' . length($euro_1);    # 1
say 'Length of $euro_2 is: ' . length($euro_2);    # 1

也可以使用use Encode qw(encode); say encode('UTF-8', $euro_1); binmode的参数来表示写入特定文件句柄的任何字符串都应编码为特定的编码。

open

这只适用于字符串。如果我们使用原始的3字节字符串binmode(STDOUT, ':encoding(utf-8)'); say $euro_1; 并使用$bytes或IO层,我们最终会得到垃圾,因为Perl将获取每个字节并将其转换为UTF8。因此encode将输出为\xE2\xC3\xA2将输出为\x82,依此类推。

但是,我们可以使用\xC2\x82函数将3字节$ bytes字符串转换为Perl内部字符表示形式的单个字符串:

Encode::Decode

一个小小的挑剔:在您的原始问题中,您声明use Encode qw(decode); my $bytes = "\xE2\x82\xAC"; my $euro_3 = decode($bytes); say 'Length of $euro_3 is ' . length($euro_3); # 1 欧元符号的 UTF-16表示。实际上有两种不同的UTF-16表示形式:UTF16BE和UTF16LE,后者使用相反的顺序:20AC

答案 1 :(得分:3)

作为您所链接的fileformat.info页面,Unicode EURO SIGN字符位于代码点20AC,可以称为U+20AC。在UTF-8中,编码为三个字节0xE2 0x82 0xAC

要将Unicode字符添加到字符串,您可以编写

"I owe you \x{20ac}160\n"

"I owe you \N{EURO SIGN}160\n"

"I owe you \N{U+20AC}160\n"

或者,如果您use utf8位于程序的顶部,则可以添加具有相同效果的文字字符

"I owe you €160\n"

每个都会将单个字符添加到具有所需代码点的字符串

如果您使用

"I owe you 80\xe2\x82\xac\n"

然后你创建了一个包含三个字符的字符串,这些字符对应于UTF-8编码的EURO SIGN字符,这是一个非常不同的东西。您可以使用Encode模块中的decode_utf8将这些字节转换为单个字符,但是您有一个UTF-8编码的字符串,这与字符串不同

这是一个示例程序

use strict;
use warnings 'all';

use open qw/ :std :encoding(UTF-8) /;

use Encode qw/ decode_utf8 :fallbacks /;

for my $s (
        "I owe you \x{20ac}160\n",
        "I owe you \N{EURO SIGN}160\n",
        "I owe you \N{U+20AC}160\n",
        do { use utf8; "I owe you €160\n" },
        decode_utf8(my $ss = "I owe you \xe2\x82\xac160\n") ) {

    print $s;
}

输出

I owe you €160
I owe you €160
I owe you €160
I owe you €160
I owe you €160

请注意,除非您在源代码中使用非ASCII字符,否则不需要use utf8,例如。您可以通过其Unicode名称(始终使用ASCII)访问字符,如上所示

  

如果我重定向到一个文件,我可以看到它按预期编码第一个欧元符号,0xe282ac,但第二个变为0xc3a2c20x82c2ac,所以不知何故它变得乱码,好像它&# 39;被编码两次。

被编码两次。您是第一次通过提供UTF-8编码" \ xe2 \ x82 \ xac"来自己编码角色。对于角色,输出文件句柄上的binmode会再次对每个字符进行编码,C3 A2E2C2 8282C2 AC

AC

答案 2 :(得分:3)

您正在构建两个不同的字符串,因此获得不同的结果应该不会令人感到意外。

您正在执行所谓的“双重编码”。您有一个已使用UTF-8编码的字符串,并且您要求Perl(使用binmodeprint)再次对其进行编码。那是你的错误。

字符串文字"\x{20ac}"生成一个单字符的字符串(0x20ac)。

$ perl -E'say length("\x{20ac}")'
1

当您使用:utf8句柄将其打印到句柄时,您指示Perl将这些字符视为Unicode代码点并使用UTF-8对其进行编码。

根据要求,Perl使用UTF-8打印以下编码:
U + 020AC EURO SIGN(€)。

$ perl -E'binmode STDOUT, ":utf8"; print "\x{20ac}"' | od -t x1
0000000 e2 82 ac
0000003

$ perl -E'binmode STDOUT, ":utf8"; say "\x{20ac}"'
€

字符串文字"\xe2\x82\xac"生成一个三个字符的字符串(0xe2, 0x82, 0xac)。

$ perl -E'say length("\xe2\x82\xac")'
3

"\xe2\x82\xac""\x{e2}\x{82}\x{ac}"相同。)

当您使用:utf8句柄将其打印到句柄时,您指示Perl将这些字符视为Unicode代码点并使用UTF-8对其进行编码。

根据要求,Perl使用UTF-8打印以下编码:
U + 000E2带有环形的拉丁文小写字母A()),
U + 00082 BREAK允许在这里和 U + 000AC NOT SIGN(¬)。

$ perl -E'binmode STDOUT, ":utf8"; print "\xe2\x82\xac"' | od -t x1
0000000 c3 a2 c2 82 c2 ac
0000006

$ perl -E'binmode STDOUT, ":utf8"; say "\xe2\x82\xac"'
�