为什么脚本语言不在Windows控制台输出Unicode?
Windows控制台已经支持Unicode至少有十年了,甚至可能从Windows NT时代就开始了。但不知为什么,一些主要的跨平台脚本语言,比如Perl和Python,输出的却都是各种8位编码,这让人很头疼。Perl会给出“打印时遇到宽字符”的警告,而Python则会出现字符映射错误并直接退出。为什么经过这么多年,它们不直接调用Win32的-W API来输出UTF-16的Unicode,而是非得通过ANSI/代码页的瓶颈呢?
难道是因为跨平台性能不够重要?还是这些语言内部使用UTF-8,觉得输出UTF-16太麻烦?又或者是-W API本身有问题,根本不能直接使用?
更新
看起来这个问题可能需要大家共同承担责任。我原以为这些脚本语言可以直接在Windows上调用wprintf
,让操作系统或运行时来处理重定向等问题。但结果发现,即使是Windows上的wprintf也会在打印到控制台之前将宽字符转换为ANSI格式!
如果这个问题已经解决,请告诉我,因为我发现的错误报告链接似乎坏了,但我的Visual C测试代码在使用wprintf时仍然失败,而使用WriteConsoleW时则成功。
更新 2
其实你可以通过C语言使用wprintf
将UTF-16打印到控制台,但前提是你得先执行_setmode(_fileno(stdout), _O_U16TEXT)
。
从C语言中,你可以在代码页设置为65001的控制台上打印UTF-8,但Perl、Python、PHP和Ruby都有一些bug,导致这无法实现。Perl和PHP在输出时会在包含至少一个宽字符的行后面添加额外的空行,导致输出混乱。Ruby的输出也有些不同的混乱情况,而Python则会崩溃。
更新 3
Node.js是第一个没有这个问题的脚本语言,开箱即用就能正常工作。
Python开发团队慢慢意识到这个问题的严重性,因为这个问题早在2007年底就被首次报告了,并在2016年经历了一波大规模的活动,旨在彻底理解并修复这个bug。
9 个回答
我得先把你们的一些问题撤回。
你知道吗:
- Windows在它的应用程序接口(API)中使用UTF-16这种编码,但在用户空间里,默认还是使用各种“有趣”的旧编码方式(比如Windows-1252、Windows-1251),而且这些编码在不同语言版本的Windows中表现得还不一样。
- 你需要对输出进行编码,选择合适的编码方式是通过一个叫做locale pragma的东西来实现的,而这个东西是基于一个叫做locale的POSIX标准,Windows和这个标准是不兼容的。
- Perl曾经支持过所谓的“宽”API。
- 微软成功地把UTF-8融入了他们的字符编码系统中,你可以通过输入相应的
chcp 65001
命令来切换你的终端编码。
我想在这个讨论中稍微补充一下 - 我使用的是捷克语本地化的Windows XP,基本上到处都在用CP1250编码。不过有趣的是,控制台却还是用老旧的DOS 852编码。
我写了一个非常简单的perl脚本,可以把utf8编码的数据打印到控制台,代码如下:
binmode STDOUT, ":utf8:encoding(cp852)";
我尝试了各种选项(包括utf16le),但只有上面的设置能正确打印带有重音的捷克字符。
编辑:我对这个问题又做了一些研究,发现了Win32::Unicode模块。这个模块提供了一个叫printW
的函数,它在输出和重定向时都能正常工作:
use utf8;
use Win32::Unicode;
binmode STDOUT, ":utf8";
printW "Příliš žluťoučký kůň úpěl ďábelské ódy";
主要的问题似乎是,在Windows上仅使用标准C库而不依赖平台特定或第三方扩展时,无法使用Unicode。你提到的那些语言源自Unix平台,它们实现Unicode的方法与C语言结合得很好(它们使用普通的char*
字符串、C语言的区域设置函数和UTF-8编码)。如果你想在C语言中使用Unicode,基本上你得写两套代码:一套是使用非标准的微软扩展,另一套是使用所有其他操作系统的标准C API函数。虽然这样做是可行的,但通常优先级不高,因为这很麻烦,而且大多数脚本语言的开发者要么讨厌Windows,要么根本不理会它。
从更技术的角度来看,大多数标准库设计者的基本假设是,所有的输入输出流在操作系统层面上本质上都是基于字节的,这在所有操作系统的文件中都是正确的,在类Unix系统的所有流中也是如此,只有Windows控制台是个例外。因此,如果想要将Windows控制台的输入输出纳入考虑,许多类库和编程语言的标准架构就必须进行很大程度的修改。
另一个更主观的观点是,微软并没有足够努力地推广Unicode。第一个支持相对不错(在当时)的Unicode的Windows操作系统是1993年发布的Windows NT 3.1,远在Linux和OS X开始支持Unicode之前。然而,这些操作系统向Unicode的过渡要顺利得多,没有什么问题。微软再次听从了销售人员的意见,而不是工程师的建议,直到2001年才淘汰技术上过时的Windows 9x;他们没有强迫开发者使用干净的Unicode接口,而是继续提供那些破损且现在不必要的8位API接口,并邀请程序员使用它(看看最近在Stack Overflow上的一些Windows API问题,大多数新手仍然在使用那些糟糕的遗留API!)。
当Unicode推出时,很多人意识到它是有用的。Unicode最初是纯16位编码,因此使用16位代码单元是很自然的。然后,微软显然说:“好吧,我们有这个16位编码,所以我们必须创建一个16位的API”,却没有意识到没有人会使用它。然而,Unix的先驱们则想:“我们如何能以高效且向后兼容的方式将其整合到当前系统中,以便人们真正使用它呢?”于是他们发明了UTF-8,这是一项出色的工程。就像Unix创建时一样,Unix的人们考虑得更多,花了更多时间,虽然经济上不太成功,但最终做对了。
我不能对Perl发表评论(但我觉得在Perl社区中,讨厌Windows的人比在Python社区中要多),不过关于Python我知道,BDFL(他也不喜欢Windows)曾表示,所有平台上都能提供足够的Unicode支持是一个主要目标。