为啥脚本语言不将 Unicode 输出到 Windows 控制台?
Posted
技术标签:
【中文标题】为啥脚本语言不将 Unicode 输出到 Windows 控制台?【英文标题】:Why don't scripting languages output Unicode to the Windows console?为什么脚本语言不将 Unicode 输出到 Windows 控制台? 【发布时间】:2011-06-23 23:02:43 【问题描述】:Windows 控制台识别 Unicode 至少有十年了,也许早在 Windows NT 就已经存在了。但是由于某种原因,包括 Perl 和 Python 在内的主要跨平台脚本语言只输出各种 8 位编码,需要很多麻烦才能解决。 Perl 给出“打印中的宽字符”警告,Python 给出charmap 错误并退出。为什么这些年来他们不只是简单地调用输出 UTF-16 Unicode 的 Win32 -W API,而不是强迫一切通过 ANSI/代码页瓶颈?
只是跨平台性能的优先级低吗?是不是这些语言在内部使用 UTF-8 并且觉得输出 UTF-16 太麻烦了?还是 -W API 本身就被破坏到无法按原样使用的程度?
更新
看来,责任可能需要由各方共同承担。我想象脚本语言可以在 Windows 上调用 wprintf
并让操作系统/运行时担心诸如重定向之类的事情。但事实证明even wprintf on Windows converts wide characters to ANSI and back before printing to the console!
请让我知道这是否已修复,因为错误报告链接似乎已损坏,但我的 Visual C 测试代码对于 wprintf 仍然失败并且对于 WriteConsoleW 成功。
更新 2
实际上,您可以使用 wprintf
从 C 将 UTF-16 打印到控制台,但前提是您先使用 _setmode(_fileno(stdout), _O_U16TEXT)
。
从 C 语言中,您可以将 UTF-8 打印到代码页设置为代码页 65001 的控制台,但是 Perl、Python、php 和 Ruby 都存在阻止这种情况的错误。 Perl 和 PHP 通过在包含至少一个宽字符的行后面添加额外的空行来破坏输出。 Ruby 的损坏输出略有不同。 Python 崩溃。
更新 3
Node.js 是第一个开箱即用且没有此问题的脚本语言。
自it was first reported back at the end of 2007 以来,Python 开发团队慢慢意识到这是一个真正的问题,并在 2016 年看到了大量活动来完全理解和完全修复该错误。
【问题讨论】:
你不能“输出 Unicode”。 Unicode 是一种在内部将字符表示为代码点的方法。要输出它,您需要某种形式的编码 - 可能是 UTF-8。 当然可以输出Unicode。在 *nix 上,输出 Unicode 的标准编码是 UTF-8。在 Windows 上,标准的输出方式是 UTF-16,除了在 Windows 世界中,当他们表示 UTF-16 时,他们说“Unicode”。这可能也适用于 Java 以及 UTF-8 不是主要的任何其他地方。 @Daniel:如果您不喜欢该术语,请将其替换为“如果满足适当的条件(字体支持等),请在控制台上打印任意 Unicode 字符”。 UTF-8 是 Unicode 标准的一部分,它的作用远不止分配代码点。 @Daniel:Unicode 有特定的术语,其中“编码”意味着“将字符表示为代码点的方法”。将此与代表“Unicode 转换格式”的 UTF 进行比较,后者是将代码点表示为字节或单词流等的过程。在 Unicode 世界之外,字符到数字(代码点)的映射以及一系列代码点的转换成一串字节或单词被模糊在一起作为“编码”。也许令人困惑和烦人,但就是这样。 node.js 是我发现的第一个脚本语言,它可以在 *nix 和 Windows 系统的控制台中使用 Unicode 开箱即用!当然,它的目的不是作为常规脚本语言,而是用于服务器大小的基于节点的东西,因此缺少脚本语言所期望的许多功能。 (It's not easy to read text line-by-line for instance.) 【参考方案1】:主要问题似乎是在 Windows 上仅使用标准 C 库而不使用平台相关或第三方扩展是不可能使用 Unicode 的。您提到的语言源自 Unix 平台,其实现 Unicode 的方法与 C 很好地融合在一起(它们使用普通的char*
字符串、C 语言环境函数和 UTF-8)。如果您想在 C 中编写 Unicode,您或多或少必须将所有内容编写两次:一次使用非标准的 Microsoft 扩展,一次使用适用于所有其他操作系统的标准 C API 函数。虽然可以这样做,但它通常没有高优先级,因为它很麻烦,而且大多数脚本语言开发人员要么讨厌要么忽略 Windows。
在技术层面,我认为大多数标准库设计人员所做的基本假设是,所有 I/O 流本质上都是基于操作系统级别的,这适用于所有操作系统上的文件,并且适用于所有类 Unix 系统上的流,Windows 控制台是唯一的例外。因此,如果要合并 Windows 控制台 I/O,必须在很大程度上修改许多类库和编程语言标准的体系结构。
另一个更主观的观点是,微软在推广 Unicode 的使用方面做得还不够。第一个(在当时)支持 Unicode 的 Windows 操作系统是 1993 年发布的 Windows NT 3.1,早在 Linux 和 OS X 支持 Unicode 之前。尽管如此,在这些操作系统中向 Unicode 的过渡更加无缝且没有问题。微软再次听取了销售人员的意见,而不是工程师的意见,并将技术上已经过时的 Windows 9x 一直保留到 2001 年;他们没有强迫开发人员使用干净的 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 支持是一个主要目标。
【讨论】:
+1 非常有用地回答一个经常让我感到沮丧的问题。 我已经接受了这个作为答案,因为它是唯一一个认真尝试从字面上回答我的问题的答案,即使我仍然没有办法在任一 Perl 中将 Unicode 输出到 Windows 控制台或 Python!但是我还有一些进一步的 cmets: wprintf() 和相关函数是标准 C 库的一部分还是纯粹的 MS 扩展? iconv() 是标准 C 库的一部分吗? Perl 或 Python 是否在某处声明它们严格遵守标准 C 库并避免可能是 wprintf() 和 iconv() 之类的扩展?顺便说一句,我之前在 C/C++ 中为 AbiWord 跨平台文字处理器完成了 Unicode,在其中我实现了编码文本保存和加载功能。但现在我更喜欢脚本语言,因为我主要做多语言文本处理。 @hippietrail:wprintf
是标准 C,但 _setmode
和 _fileno
不是。通常(但并非总是)Microsoft 在非标准扩展前加上下划线。 iconv
不是 C 标准的一部分。 Perl 和 Python 都没有使用没有扩展的纯 C,因为即使是一些非常常见的事情,例如读取目录内容或创建链接,也没有包含在 C 标准中。 Lua 在其标准库中仅使用标准 C 函数,但即便如此它也必须使用扩展来动态加载模块。
如果 Perl 和 Python 不使用 Microsoft 扩展 Unicode 输出,你必须自己做。 Windows 中的控制台输出总是需要经过WriteConsoleW
,没有别的办法。参见例如this long discussion(许多贡献者错误地认为 Unicode 在 Windows 控制台中不起作用,或者与代码页有关)。它包含一个指向a possible fix 的链接,但通常必须重写 Python 标准库。【参考方案2】:
对讨论的贡献很小 - 我正在运行捷克本地化的 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";
【讨论】:
与西里尔字母相同。所有 8 位 API 都使用 CP1251,他们称之为“ANSI 编码”;并且控制台 API 使用 CP866 — 旧的,来自 DOS 旧代码页的时代;他们称之为“OEM 编码”。 旧的 IBM 代码页(例如 852)用于兼容性,因为它们包含许多旧 DOS 应用程序中使用的图形字符 - 其中许多仍在使用!较新的代码页(例如 1250)是为 Windows 引入的,不包括控制台应用程序所需的旧图形字符。 @bvr: "chcp 65001" 支持 UTF-8,但似乎没有得到很好的支持。它会导致 Perl 的输出异常中断,并导致 Python 崩溃! @bvr:是的,我得到了同样的结果。我不确定这是 100% Windows 的错还是 Windows 和 Perl 之间的某些交互,尽管我认为是前者。我很确定这是由于字符串函数假设字节数等于字符数。 @hippietrail 我找到了正确工作的方法 - 使用 Win32::Unicode 模块。在我的答案中添加了一个示例。【参考方案3】:我必须回答你的许多问题。
你知道吗
Windows 将 UTF-16 用于其 API,但仍默认使用用户空间中的各种“有趣”传统编码(例如 Windows-1252、Windows-1251),包括文件名,这对于 Windows 的许多本地化版本有所不同?李> 您需要对输出进行编码,并且为系统选择合适的编码是通过locale pragma 实现的,并且有一个名为locale 的POSIX 标准是在其上构建的,而Windows 与它不兼容? Perl already supported 所谓的“宽”API 一次? Microsoft 设法将 UTF-8 适配到他们的字符编码代码页系统中,您可以通过发出适当的chcp 65001
命令来切换终端?
【讨论】:
旧的 API 函数仍然可用,但它们除了将字符串转换为 UTF-16 和从 UTF-16 转换并调用 UTF-16 函数外,什么也不做。现在任何正常的 Windows 应用程序都直接使用 UTF-16 函数。 我知道 Windows 使用 UTF-16 作为其 API,但您对传统编码的看法是错误的。它们根本不是默认的,只是为了支持遗留的东西。内部所有内容都是 UTF-16,包括文件名,旧文件系统除外。 @hippietrail:我的评论是对短语“但仍默认为用户空间中的各种“有趣”遗留编码(例如 Windows-1252、Windows-1251)”的补充,我think 并不完全正确,因为遗留函数并不比 UTF-16 更默认。 我不知道有多少错误的错误信息会导致 6 票赞成! * 您知道 Windows 正式符合 POSIX 标准吗? * 你知道代码页 65001 在 Windows 7 中的控制台中完全被破坏了吗? Perl 可以使用它,但似乎存在字符长度与字节长度的错误,这会导致额外的空白行和长行的结尾再次输出。 Python 简直崩溃了。如果它确实有效,我会认为它是一个有用的解决方法,但不是从所谓的跨平台脚本语言输出 Unicode 的真正解决方案。【参考方案4】:Michael Kaplan 发表了一系列关于 cmd
控制台和 Unicode 的博文,这些博文可能提供了丰富的信息(但并未真正回答您的问题):
Conventional wisdom is retarded, aka What the @#%&* is _O_U16TEXT?
Anyone who says the console can't do Unicode isn't as smart as they think they are
A confluence of circumstances leaves a stone unturned...
PS:感谢@Jeff 找到archive.org 链接。
【讨论】:
Michael Kaplan 的博客已被 Microsoft 删除。以下是相应的存档:-Conventional wisdom is retarded, aka What the @#%&* is _O_U16TEXT?-Anyone who says the console can't do Unicode isn't as smart as they think they are (继续,评论太长)-@987654327@ @Jeff 更新了帖子。谢谢。【参考方案5】:您确定您的脚本会在其他平台上正确输出 Unicode 吗? “打印中的宽字符”警告让我非常怀疑。
我建议你看看这个overview
【讨论】:
这实际上是一个有效的响应。如果您从 Perl 收到“打印中的宽字符”警告,则您的代码不正确并且在所有系统上都损坏了。 好吧,如果我知道我正在打印到 UTF-8 控制台,就像在 *nix 上一样,我可以执行 "binmode STDOUT, ':utf8'" 但在 Windows 上,即使代码为 "binmode STDOUT, ':utf16'" 不会抛出任何错误,它也不起作用。因此,在跨平台代码中,除非您有实际的修复建议,否则事情处于非常站不住脚的位置。【参考方案6】:为什么这么多年过去了 他们不只是简单地调用 Win32 -W 输出 UTF-16 Unicode 的 API 而不是强迫一切通过 ANSI/代码页瓶颈?
因为 Perl 和 Python 不是 Windows 程序。它们是 Unix 程序,碰巧大部分都移植到了 Windows 上。因此,除非必要,否则他们不喜欢调用 Win32 函数。对于基于字节的 I/O,没有必要;这可以通过标准 C 库来完成。基于 UTF-16 的 I/O 是一种特殊情况。
或者 -W API 本身就被破坏了 到了他们不可能的程度 按原样使用?
我不会说 -W API 天生就坏了,就像我说微软在 C(++) 中处理 Unicode 的方法天生就坏了一样。
无论某些 Windows 开发人员多么坚持程序应该使用wchar_t
而不是char
,切换的障碍太多了:
wchar_t
,在其他地方使用 UTF-32 wchar_t
。 (新的 char16_t
和 char32_t
类型可能会有所帮助。)
_wfopen
、_wstat
等 UTF-16 文件名函数的非标准性限制了在跨平台代码中使用 wchar_t
的能力。
教育。每个人都用printf("Hello, world!\n");
学习C,而不是wprintf(L"Hello, world!\n");
。在附录 A.13 之前,我在大学使用的 C 教科书甚至从未提及宽字符。
现有无数行代码使用char*
字符串。
【讨论】:
Perl 和 Python 显然是来自 *nix 的移植,但在 Python 自己的网站 www.python.org 上,他们并没有淡化对 Windows 的支持,事实上他们首先列出了它! “Python 可在 Windows、Linux/Unix、Mac OS X 上运行,并已移植到 Java 和 .NET 虚拟机上。” (Perl 的网站不那么粗体)。也许他们应该谦虚一点,承认 Windows 是二等公民,或者努力在 OS 和解释器之间的文本移动边缘调用 iconv() / WideCharToMultiByte() / MultiByteToWideChar()。 我不得不承认,我一直认为_wfopen
的含义更多,嗯,脏话。 ☺【参考方案7】:
为了让 Perl 以这种方式完全支持 Windows,每次调用 print
printf
say
warn
和 die
都必须修改。
一旦您确定了这一点,您就必须使用一组完全不同的 API 函数。
如果您真的想了解正确执行此操作所涉及的所有内容,请查看 source 的 Win32::Unicode::Console。
在 Linux、OpenBSD、FreeBSD 和类似操作系统上,您通常只需在 STDOUT
和 STDERR
文件句柄上调用 binmode
。
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
这里假设终端使用的是 UTF-8 编码。
【讨论】:
好吧,就像有些人理论上可能在没有全功能支持的情况下在 Windows 95 上运行 Perl 一样,理论上有些人可能在终端设置为其他编码的情况下运行 *nix,尤其是日本用户。在这种情况下,仅仅调用 binmode 是不够的。我希望 Perl 可以只调用 wprintf 并且 C 库正确处理控制台 UTF-16 和重定向。如果 C 库被破坏,那么我当然会免除 Perl 的任何责任。【参考方案8】:对于 Python,tracker 中的相关问题是http://bugs.python.org/issue1602(如 cmets 中所述)。请注意,它开放 7 年。我尝试将工作解决方案(基于问题中的信息)发布为 Python 包:https://github.com/Drekin/win-unicode-console、https://pypi.python.org/pypi/win_unicode_console。
【讨论】:
【参考方案9】:Unicode issues in Perl
介绍 Win32 控制台如何与 Perl 一起工作,以及从 ANSI 到 Unicode 的幕后转码;尽管这不仅是 Perl 问题,而且会影响其他语言
【讨论】:
以上是关于为啥脚本语言不将 Unicode 输出到 Windows 控制台?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 StandardScaler 不将元数据附加到输出列?