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

Posted

技术标签:

【中文标题】当我的 Perl 程序在 cmd.exe 中输出 UTF-8 编码字符串时,为啥我会重复最后一个八位字节?【英文标题】:Why am I getting the last octet repeated when my Perl program outputs a UTF-8 encoded string in cmd.exe?当我的 Perl 程序在 cmd.exe 中输出 UTF-8 编码字符串时,为什么我会重复最后一个八位字节? 【发布时间】:2014-06-18 10:13:10 【问题描述】:

更新

正如@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
活动代码页:65001

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

Perl 版本:

C:\...> perl -e "打印 qq\xce\xb1\xce\xb2\xce\xb3\n"
αβγ
 �

据我所知,最后一个八位字节 0xb3 再次在另一行输出,正在转换为 U+FFFD

请注意,重定向输出会消除这种影响。

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

C:\…> ​​perl -e "打印 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

我在使用自建 perl 5.18.2 和 ActiveState 5.16.3 的 Windows 8.1 Pro 64 位和 Windows Vista Home 32 位的 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 "打印 qq\xce\xb1\xce\xb2\xce\xb3\n"
╬▒╬▓╬│

当code page is set to 65001code page is set to 65001从perl程序打印时,是什么导致最后一个八位字节输出两次?

PS:我在my blog 上有更多信息和截图。对于这个问题,我尝试将所有内容提炼成最简单的情况。

PPS:省略 \n 会产生更有趣的结果:

C:\…> ​​perl -e "打印 qq\xce\xb1\xce\xb2\xce\xb3xyz"
αβγxyzxyz
C:\...> perl -e "打印 qq\xce\xb1\xce\xb2\xce\xb3"
αβγ·γ·

【问题讨论】:

我的猜测:Windows shell 中某处的错误。 @FilipeGonçalves 鉴于 C 版本和 syswrite 都按预期工作,我怀疑这是 Perl 的 :crlf IO 层和代码页 65001 之间的交互。但是,我不知道确切的位置看。欢迎指点。 使用perlbug提交错误报告 如果你忽略了\n 会发生什么? stdout,在 Windows 上的文本模式下,是否将 '\n\ 转换为 "\r\n"?不知道这将如何解释这一点,但另一个想法。 【参考方案1】:

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

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

这似乎表明: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 以进一步诊断此问题。

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

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

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

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

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

--- - Unix - '' - 0x00201200 -crlf - '' - 0x00405200 - 编码 - UTF8 - 0x00409200

正如预期的那样,unix 层没有设置CRLF 标志。

【讨论】:

你可能想试试我的 PerlIO::Layers 而不是使用 PerlIO::get_layers,它会提供更友好的输出。 :crlf 层中的错误?你肯定在开玩笑。 /s @Ether 当输出到 cmd.exe 时,它​​在我看来就像 Windows 上的 unix 层中的一个错误。但是,我不知道为什么PERLIO_F_CRLF is getting set on the bottom-most unix layer。

以上是关于当我的 Perl 程序在 cmd.exe 中输出 UTF-8 编码字符串时,为啥我会重复最后一个八位字节?的主要内容,如果未能解决你的问题,请参考以下文章

Windows cmd.exe 命令传递 Perl 系统函数在某些情况下需要双引号吗?

错误MSB6006:“cmd.exe”退出,运行QT应用程序的代码1

Visual Studio C++ 执行 cmd.exe 而不是我的程序 [关闭]

从另一个 cmd.exe 提示符中创建一个新的 cmd.exe 窗口

perl 复制exe文件的简单方法

使c ++程序以交互方式将输入输出传递给Windows命令提示符