我正在测试 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)
到现在为止一切都很好。
我还安装了适用于 Windows 的 Git 及其类似 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
实例的一些实现细节还是什么?
答案1
(请注意,在同一个终端中 - 我甚至没有打开 bash.exe 可执行文件)
这仍然是同一个终端。cmd.exe 和 bash.exe 本身都不是终端——你在Windows 控制台(Conhost)Windows 自动为“控制台”可执行文件生成它。
这Windows 控制台它与通常的终端并不完全相同,它不仅仅使用 stdio 作为其唯一接口,它还拥有一整套 API。与 Windows 中的大多数东西一样,它以 UTF-16 作为其主要文本编码。
例如,尽管程序可以使用普通的 WriteFile() 将文本输出到其标准输出,但是还有一个专用的 WriteConsole() 函数,它(与大多数 Windows API 一样)有两个版本:面向字节的 WriteConsoleA(),它需要当前 ANSI/OEM 编码中的数据,以及面向 Unicode 的 WriteConsoleW(),它始终采用 UTF-16。
因此,如果程序知道它们正在处理已知编码的文本,并且它们正在写入控制台,则它们不需要依赖“当前 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 需要在退出时明确恢复。)