我正在测试与字符集编码相关的 Windows 终端 (cmd.exe) 的行为。我有一些带有西班牙文本的几种编码(Win1252、CP437、UTF-8 等)的测试文件:“ qué tal
”
我在我的 Windows 10 机器上打开一个 CMD.exe 终端,使用默认的 CP 437 代码页(我在终端窗口属性中检查)。而且,确实,该type
命令给出了预期的输出:对 CP-437 正确,仅
C:\temp > type testfile-cp437.txt
qué tal (OK)
C:\temp > type testfile-utf8.txt
qué tal (WRONG)
到目前为止一切都很好。
我还安装了 Git for Windows 及其类似 linux 的二进制文件。
现在,我运行它cat.exe
(在同一个终端中,请注意 - 我什至不打开bash.exe
可执行文件),现在结果不同了。似乎一切都适用于 UTF-8
C:\temp > C:\Git\usr\bin\cat.exe testfile-cp437.txt
qu□ tal (WRONG)
C:\temp > C:\Git\usr\bin\cat.exe testfile-utf8.txt
qué tal (OK)
为什么会这样?我希望该cat
命令只是将字节发送到终端,因此结果应该是相同的。字节到 UTF-8 的解码在哪里发生?谁以及为什么选择 UTF-8 编码?这是这个cat
实例的一些实现细节还是什么?
那仍然是同一个终端。cmd.exe 和 bash.exe 本身都不是终端——您在Windows 控制台(Conhost) 中执行所有操作,Windows 会自动为“控制台”可执行文件生成该控制台。
Windows 控制台与您通常的终端不同,它不仅使用 stdio 作为其唯一接口——它周围还有一个完整的 API。和 Windows 中的大多数东西一样,它处理 UTF-16 作为其主要文本编码。
例如,虽然程序可以使用普通的 WriteFile() 将文本输出到其标准输出,但也有一个专用的 WriteConsole() 函数,它(与大多数 Windows API 一样)有两个版本:面向字节的 WriteConsoleA(),它期望数据在当前的 ANSI/OEM 编码,以及始终采用 UTF-16 的面向 Unicode 的 WriteConsoleW()。
因此,如果程序知道它们正在处理已知编码的文本,并且如果它们正在写入控制台,它们就不需要依赖“当前 OEM 代码页”——程序可以自己转换为 UTF -16 然后使用 WriteConsoleW() 直接输出 Unicode 格式的文本。
(即使 Cmd 的内置
type
命令也会这样做:如果它检测到您的文件具有 UTF-16 BOM,它会将其内容输出为 Unicode ,而不管活动代码页如何。)Git for Windows 中的工具是使用 MinGW 运行时编译的,与 Cygwin 一样,它试图消除 POSIX 和 Windows 环境之间的某些差异。似乎 MinGW 的 stdio 层对 Windows 控制台有特殊处理——请记住,Git 经常处理 UTF-8 数据,因此它在为 CP437 设置的控制台中无法正常工作——因此每当 MinGW 检测到它正在向其写入文本时控制台,它将自动从 UTF-8 1转换为 UTF-16 并使用 WriteConsoleW() 2直接将其输出为 Unicode 。
这样 Git.exe 本身就不需要担心 OEM 代码页——例如
git log
,可以简单地输出 UTF-8 编码的作者姓名或按原样提交消息(就像在 Linux 上一样),并让 MinGW 运行时神奇地将其转换为 Windows 兼容Unicode,绕过 OEM 代码页转换,否则会乱码。1(MinGW 实际上根据 POSIX 语言环境设置执行此转换,因此如果您将环境变量
LANG
或LC_CTYPE
环境变量设置为类似的C.cp437
,您将看到 MSYS 工具处理所有文本,就好像它在 CP437 中一样。)2(某些程序也可能使用 SetConsoleOutputCP() 将控制台临时切换到实际的 UTF-8 作为“OEM”代码页——但 MinGW 更有可能使用 WriteConsoleW(),因为它在程序之后没有任何持久影响崩溃,而输出 CP 需要在退出时显式恢复。)