当我的Perl程序在cmd.exe中输出UTF-8编码的字符串时,为什么我会重复最后一个八位字节?

Sin*_*nür 19 c windows perl utf-8

更新

正如@ikegami建议的那样,我将此报告为一个错误.

错误#121783 for perl5:Windows:cmd.exe中的UTF-8编码输出,代码页65001导致意外输出

考虑以下C和Perl程序,它们都在标准输出上输出字符串"αβγ"的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;
}
Run Code Online (Sandbox Code Playgroud) 输出:
C:\…> chcp 65001
Active code page: 65001

C:\…> cttt.exe
???

Perl版本:

C:\…>  perl -e "print qq{\xce\xb1\xce\xb2\xce\xb3\n}"
???
?

据我所知,最后一个八位字节0xb3正在另一行输出,正在被翻译成另一行U+FFFD.

请注意,重定向输出会消除此效果.

我还可以验证它是重复的最后一个八位字节:

C:\…>  perl -e "print qq{\xce\xb1\xce\xb2\xce\xb3xyz\n}"
???xyz
z

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

C:\…>  perl -e "syswrite STDOUT, qq{\xce\xb1\xce\xb2\xce\xb3xyz\n}"
???xyz

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

我没有在Cygwin,Linux或Mac OS X环境中看到问题.此外,Cygwin的perl 5.14.4在cmd.exe中生成正确的输出.

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

C:\…> chcp 437
Active code page: 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}"
??????

Sin*_*nür 5

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

use utf8;
use strict;
use warnings;
use warnings qw(FATAL utf8);

binmode(STDOUT, ":unix:encoding(utf8):crlf");

print '???xyz', "\n";
Run Code Online (Sandbox Code Playgroud)

输出:

C:\…> chcp 65001
Active code page: 65001
C:\…> perl pttt.pl
???xyz

这似乎向我表明这: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";
Run Code Online (Sandbox Code Playgroud)

给我:

---
- unix
- ''
- 0x01205200
- crlf
- ''
- 0x00c85200
- unix
- ''
- 0x01201200
- encoding
- utf8
- 0x00c89200
- crlf
- ''
- 0x00c8d200
???xyz

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

进一步研究了这一点.以下是该帖子的一些观察结果:

第一unix层的标志是0x01205200 = CANWRITE | TRUNCATE | CRLF | OPEN | NOTREG.为什么在Windows上CRLFunix图层设置?我不知道内部是否足以理解这一点.

但是,第二unix层的标志是我的显式推送的标志binmode,是0x01201200 = 0x01205200&~CRLF.这对我来说是有意义的.

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

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

---
- unix
- ''
- 0x00201200
- crlf
- ''
- 0x00405200
- encoding
- utf8
- 0x00409200

正如所料,该unix层未设置CRLF标志.