【问题标题】:In Perl, why is a utf-8 string printed differently when split into characters?在Perl中,为什么将在分成字符时以不同的方式打印UTF-8字符串?
【发布时间】:2014-09-07 17:26:18
【问题描述】:

一个特殊构造的字符串在我使用时会以不同的方式打印出来

print $b;

print for split //, $b;

一个最小的例子是:

#!perl
use warnings;
use strict;

use Encode;

my $b = decode 'utf8', "\x{C3}\x{A1}\x{E2}\x{80}\x{93}\x{C3}\x{A1}"; # 'á–á' in Unicode;

print $b, "\n";
print for split //, $b

控制台屏幕上的输出(我想我用的是cp860)是:

Wide character in print at xx.pl line 9.
├íÔÇô├í
Wide character in print at xx.pl line 10.
ßÔÇôß

或十六进制:

C3 A1 E2 80 93 C3 A1 
E1 E2 80 93 E1

(当然是由0D 0A分隔,即\r\n)。

问题是为什么字符呈现不同?

令人惊讶的是,没有 em-dash,效果就消失了。对于较长的字符串,可以看到效果,如下例所示。

对于字符串 'Él es mi tío Toño –Antonio Pérez'(在程序中输入为 Unicode;注意这两行是不同的!):

Wide character in print at xx.pl line 14.
├ël es mi t├¡o To├▒o ÔÇôAntonio P├®rez
Wide character in print at xx.pl line 15.
╔l es mi tÝo To±o ÔÇôAntonio PÚrez

但是,对于字符串 'Él es mi tío Toño, Antonio Pérez':

╔l es mi tÝo To±o, Antonio PÚrez
╔l es mi tÝo To±o, Antonio PÚrez

没有什么不好的事情发生,两条线以相同的方式呈现。唯一的区别是存在一个破折号,即'\x{E2}\x{80}\x{93}'!

另外,print join '', split //, $b; 给出的结果与print $b; 相同,但与print for split //, $b; 不同。

如果我添加binmode STDOUT, 'utf8';,那么两个输出都是ÔÇô├í = E2 80 93 C3 A1。

所以我的问题不完全是关于如何避免它,而是关于为什么会发生这种情况:为什么相同的字符串在拆分时表现不同?

显然在这两种情况下utf8 标志都是打开的。这是一个更详细的程序,显示了有关两个字符串的更多信息:decode 之前的$adecode 之后的$b

#!perl
use warnings;
use strict;
use 5.010;

use Encode;

my $a = "\x{C3}\x{A1}\x{E2}\x{80}\x{93}\x{C3}\x{A1}"; # 'á–á' in Unicode;
my $b = decode 'utf8', $a;

say '------- length and utf8 ---------';
say "Length (a)=", length $a, ", is_uft8(a)=", (Encode::is_utf8 ($a) // 'no'), ".";
say "Length (b)=", length $b, ", is_uft8(b)=", (Encode::is_utf8 ($b) // 'no'), ".";
say '------- as a variable---------';
say "a: $a";
say "b: $b", ' <== *** WHY?! ***';
say '------- split ---------';
print "a: "; print for split //, $a; say '';
print "b: "; print for split //, $b; say ' <== *** DIFFERENT! ***';
say '------- split with spaces ---------';
print "a: "; print "[$_] " for split //, $a; say '';
print "b: "; print "[$_] " for split //, $b; say '';
say '------- split with properties ---------';
print "a: "; print "[$_ is_utf=" . Encode::is_utf8 ($_) . " length=" . length ($_) . "] " for split //, $a; say '';
print "b: "; print "[$_ is_utf=" . Encode::is_utf8 ($_) . " length=" . length ($_) . "] " for split //, $b; say '';
say '------- ord() ---------';
print "a: "; print ord, " " for split //, $a; say '';
print "b: "; print ord, " " for split //, $b; say '';

这是它在控制台上的输出:

------- length and utf8 ---------
Length (a)=7, is_uft8(a)=.
Length (b)=3, is_uft8(b)=1.
------- as a variable---------
a: ├íÔÇô├í
Wide character in say at x.pl line 16.
b: ├íÔÇô├í <== *** WHY?! ***
------- split ---------
a: ├íÔÇô├í
Wide character in print at x.pl line 19.
b: ßÔÇôß <== *** DIFFERENT! ***
------- split with spaces ---------
a: [├] [í] [Ô] [Ç] [ô] [├] [í]
Wide character in print at x.pl line 22.
b: [ß] [ÔÇô] [ß]
------- split with properties ---------
a: [├ is_utf= length=1] [í is_utf= length=1] [Ô is_utf= length=1] [Ç is_utf= length=1] [ô is_utf= length=1] [├ is_utf= length=1] [í is_utf= length=1]
Wide character in print at x.pl line 25.
b: [ß is_utf=1 length=1] [ÔÇô is_utf=1 length=1] [ß is_utf=1 length=1]
------- ord() ---------
a: 195 161 226 128 147 195 161
b: 225 8211 225

【问题讨论】:

  • 请避免使用my $amy $b。它可以搞砸sort 和一些常用的库子。
  • 是的,没错!我使用它们只是为了便于阅读 :-) 在实际程序中不得使用。

标签: string perl unicode utf-8 language-lawyer


【解决方案1】:

区别在于被打印的字符串是否包含>255的任何字符。 print 只知道你在那种情况下做错了什么[1]


给定一个没有:encoding 的句柄,print 需要一个字节串(字符串≤255)。

当它没有接收到字节(字符串包含 >255 个字符)时,它会通知您错误(“宽字符”)并猜测您打算使用 UTF-8 对字符串进行编码。

您可以将没有:encoding 的句柄上的print 视为执行以下操作:

if ($s =~ /[^\x00-\xFF]/) {
   warn("Wide character");
   utf8::encode($s);
}

my $b = decode 'utf8', "\x{C3}\x{A1}\x{E2}\x{80}\x{93}\x{C3}\x{A1}";

一样
my $b = "\xE1\x{2013}\xE1";

因此,你正在做

print "\xE1\x{2013}\xE1";
print "\xE1";
print "\x{2013}";
print "\xE1";
  1. print "\xE1\x{2013}\xE1";   # Wide char! C3 A1 E2 80 93 C3 A1
    

    Perl 注意到您忘记编码、警告您并打印使用 UTF-8 编码的字符串。

  2. print "\xE1";               # E1
    

    Perl 无法知道您忘记了编码,因此它会打印您要求它打印的内容。

  3. print "\x{2013}";           # Wide char! E2 80 93
    

    Perl 注意到您忘记编码、警告您并打印使用 UTF-8 编码的字符串。


脚注

  1. 存储格式的选择(由is_utf8 返回)永远不会产生影响。 print 正确地不受它的影响。

    utf8::downgrade( my $d = chr(0xE1) );  print($d);  # UTF8=0 prints E1
    utf8::upgrade(   my $u = chr(0xE1) );  print($u);  # UTF8=1 prints E1
    

【讨论】:

  • 啊哈,现在我明白了!非常简单:print 将整个字符串解释为utf8,如果该字符串包含至少一个高于 ASCII 范围的字符。当我拆分字符串时,print 仅将那些高于 ASCII 范围的单个字符(1 个字符的字符串)解释为 utf8,而将其他字符解释为 ASCII。
  • 如果字符串包含至少一个高于 255 的字符,print 会将整个字符串解释为 Unicode 代码点需要编码。 (UTF-8 是它的结尾;而不是它的开头。ASCII 编码只有 128 个字符,print 并不像提到 ASCII 所暗示的那样假设字符是文本。)
  • 在我理解您的意思之前,我必须阅读此内容 100 遍。默认情况下,perl 不会打印出任何大于 255 的代码点,因此它将整个字符串 BACK 转换为可以打印的字节(代码点
  • Re "默认情况下,perl 不会打印出任何 > 255 的代码点",正确。它不能。文件只能包含字节。除了字节之外的任何东西都需要转换为字节。 :encoding 告诉 Perl 如何做到这一点。
  • Re “我认为直接打印代码点是有意义的。”这是没有意义的。 0x2660 不能放在文件中。
猜你喜欢
  • 2020-11-29
  • 2014-05-17
  • 2019-04-09
  • 2019-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-03
  • 2021-09-08
相关资源
最近更新 更多