为什么当我的Perl程序在cmd.exe中输出一个UTF-8编码的string时,我得到了最后一个八位字节?

更新

正如@ikegami所build议的,我把这个报告为一个bug。

用于perl5的Bug#121783:Windows:带有代码页65001的cmd.exe中的UTF-8编码输出会导致意外的输出

考虑以下C和Perl程序,它们在标准输出上输出string“αβγ”的UTF-8编码:

C版本:

#include <stdio.h> int main(void) { /* UTF-8 encoded alpha, beta, gamma */ char x[] = { 0xce, 0xb1, 0xce, 0xb2, 0xce, 0xb3, 0x00 }; puts(x); return 0; } 

输出:

  C:\ ...> chcp 65001
有效代码页:65001

 C:\ ...> cttt.exe
 αβγ 

Perl版本:

  C:\ ...> perl -e“print qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3 \ n}”
 αβγ
 

从我可以告诉,最后一个字节, 0xb3被输出再次,在另一行,正在被转换为U+FFFD

请注意,redirect输出消除了这种影响。

我也可以validation它是重复的最后一个八位字节:

  C:\ ...> perl -e“print qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3xyz \ n}”
 αβγxyz
 ž 

另一方面, syswrite避免了这个问题。

  C:\ ...> perl -e“syswrite STDOUT,qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3xyz \ n}”
 αβγxyz 

我已经在Windows 8.1 Pro 64位和Windows Vista Home 32位上使用自build的perl 5.18.2和ActiveState的5.16.3在cmd.exe窗口中观察到这一点。

我在Cygwin,Linux或Mac OS X环境中看不到问题。 另外,Cygwin的perl 5.14.4在cmd.exe中产生正确的输出。

另外,当代码页被设置为437时,C和Perl版本的输出是相同的:

  C:\ ...> chcp 437
有效代码页:437

 C:\ ...> cttt.exe
 ╬▒╬▓╬│

 C:\ ...> perl -e“print qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3 \ n}”
 ╬▒╬▓╬│ 

当代码页被设置为65001时,在从cmd.exe的perl程序打印时导致最后一个字节输出两次的原因是什么?

PS: 我的博客上有更多的信息和屏幕截图。 对于这个问题,我试图提炼出最简单的情况。

PPS:离开\n导致更有趣的事情:

  C:\ ...> perl -e“print qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3xyz}”
 αβγxyzxyz 
  C:\ ...> perl -e“print qq {\ xce \ xb1 \ xce \ xb2 \ xce \ xb3}”
 αβγγ 

以下程序产生正确的输出:

 use utf8; use strict; use warnings; use warnings qw(FATAL utf8); binmode(STDOUT, ":unix:encoding(utf8):crlf"); print 'αβγxyz', "\n"; 

输出:

  C:\ ...> chcp 65001
有效代码页:65001
 C:\ ...> perl pttt.pl
 αβγxyz 

这似乎表明我有一些funkiness :crlf crlf图层。 在这一点上,我不明白内部的足够聪明的评论。

经过多次实验,我得出结论,如果控制台已经设置为65001代码页, binmode(STDOUT, ":unix:encoding(utf8):crlf"); 将工作”。 但是请注意以下几点:

 binmode(STDOUT, ":unix:encoding(utf8):crlf"); print Dump [ map { my $x = defined($_) ? $_ : ''; $x =~ s/\A([0-9]+)\z/sprintf '0x%08x', $1/eg; $x; } PerlIO::get_layers(STDOUT, details => 1) ]; print "αβγxyz\n"; 

给我:

  ---
 -  unix
 - ''
 -  0x01205200
 -  crlf
 - ''
 -  0x00c85200
 -  unix
 - ''
 -  0x01201200
 - 编码
 -  utf8
 -  0x00c89200
 -  crlf
 - ''
 -  0x00c8d200
 αβγxyz 

和以前一样,我不知道这个的全部后果。 我打算在某个时候构建一个调试perl来进一步诊断。

我进一步审查了这一点 。 以下是这篇文章的一些观察:

第一个unix层的标志是0x01205200 = CANWRITE | TRUNCATE | CRLF | OPEN | NOTREG 0x01205200 = CANWRITE | TRUNCATE | CRLF | OPEN | NOTREG 0x01205200 = CANWRITE | TRUNCATE | CRLF | OPEN | NOTREG 为什么CRLF在Windows上设置为unix层? 我不知道内部足够了解这一点。

然而,第二个unix层的标志,我明确的binmode推送的binmode是0x01201200 = 0x01205200&〜CRLF。 这对我来说是有意义的。

第一个0x00c85200 = CANWRITE | TRUNCATE | CRLF | LINEBUF | FASTGETS | TTY层的标志是0x00c85200 = CANWRITE | TRUNCATE | CRLF | LINEBUF | FASTGETS | TTY 0x00c85200 = CANWRITE | TRUNCATE | CRLF | LINEBUF | FASTGETS | TTY 0x00c85200 = CANWRITE | TRUNCATE | CRLF | LINEBUF | FASTGETS | TTY :encoding(utf8)图层之后推送的第二layer标志是0x00c8d200 = 0x00c85200 | UTF8 0x00c8d200 = 0x00c85200 | UTF8

现在,如果我使用open my $fh, '>:encoding(utf8)', 'ttt'打开一个文件并转储相同的信息,我会得到:

  ---
 -  unix
 - ''
 -  0x00201200
 -  crlf
 - ''
 -  0x00405200
 - 编码
 -  utf8
 -  0x00409200 

正如所料, unix层不会设置CRLF标志。