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

时间:2014-05-01 20:31:11

标签: c windows perl utf-8

更新

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

Bug #121783 for perl5: Windows: UTF-8 encoded output in cmd.exe with code page 65001 causes unexpected output

考虑以下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;
}
输出:
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}"
╬▒╬▓╬│

code page is set to 65001时,在cmd.exe中从perl程序打印时,导致上一个八位字节输出的原因是什么?

PS:我在my blog上有更多信息和截图。对于这个问题,我试图将一切都提炼到最简单的情况。

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}"
αβγ�γ�

1 个答案:

答案 0 :(得分:5)

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

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

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

print 'αβγxyz', "\n";

输出:

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";

给了我:

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

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

examined this a little further。以下是该帖子的一些观察结果:

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

然而,第二个unix图层的标记是我的显式binmode推送的标记,是0x01201200 = 0x01205200&amp; 〜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标记。