Answers:
是的,这令人沮丧-有时type
其他程序会打印乱码,有时却不会。
首先,仅当当前控制台字体包含字符时,才会显示Unicode 字符。因此,请使用Lucida Console之类的TrueType字体代替默认的Raster字体。
但是,如果控制台字体不包含您要显示的字符,您将看到问号而不是乱码。当您出现乱码时,除了字体设置之外,还有更多的事情要做。
当程序使用标准的C库I / O功能(如)时printf
,程序的输出编码必须与控制台的输出编码匹配,否则您将变得乱码。chcp
显示并设置当前代码页。使用标准C库I / O功能的所有输出均被视为,显示在的代码页中chcp
。
将程序的输出编码与控制台的输出编码进行匹配可以通过两种不同的方式来完成:
程序可以使用chcp
或
获取控制台的当前代码页GetConsoleOutputCP
,并将其配置为以该编码输出,或者
您或程序可以使用chcp
或
SetConsoleOutputCP
匹配程序的默认输出编码来设置控制台的当前代码页。
但是,使用Win32 API的程序可以使用来将UTF-16LE字符串直接写入控制台
WriteConsoleW
。这是在不设置代码页的情况下获得正确输出的唯一方法。即使使用该功能,如果字符串开头不是UTF-16LE编码,则Win32程序也必须将正确的代码页传递给
MultiByteToWideChar
。同样,WriteConsoleW
如果程序的输出被重定向,它将不起作用。在这种情况下,需要更多的摆弄。
type
之所以可以工作,是因为它会检查每个文件的开始以获取UTF-16LE 字节顺序标记(BOM),即bytes 0xFF 0xFE
。如果找到这样的标记,则WriteConsoleW
无论当前代码页如何,它都会使用来显示文件中的Unicode字符。但是,如果要type
查看没有UTF-16LE BOM的任何文件,或者要在任何不调用命令的情况下使用非ASCII字符,WriteConsoleW
则需要将控制台代码页和程序输出编码设置为相互匹配。
我们如何找到答案?
这是一个包含Unicode字符的测试文件:
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
这是一个Java程序,用于以一堆不同的Unicode编码输出测试文件。它可以是任何编程语言。仅将ASCII字符或编码字节打印到stdout
。
import java.io.*;
public class Foo {
private static final String BOM = "\ufeff";
private static final String TEST_STRING
= "ASCII abcde xyz\n"
+ "German äöü ÄÖÜ ß\n"
+ "Polish ąęźżńł\n"
+ "Russian абвгдеж эюя\n"
+ "CJK 你好\n";
public static void main(String[] args)
throws Exception
{
String[] encodings = new String[] {
"UTF-8", "UTF-16LE", "UTF-16BE", "UTF-32LE", "UTF-32BE" };
for (String encoding: encodings) {
System.out.println("== " + encoding);
for (boolean writeBom: new Boolean[] {false, true}) {
System.out.println(writeBom ? "= bom" : "= no bom");
String output = (writeBom ? BOM : "") + TEST_STRING;
byte[] bytes = output.getBytes(encoding);
System.out.write(bytes);
FileOutputStream out = new FileOutputStream("uc-test-"
+ encoding + (writeBom ? "-bom.txt" : "-nobom.txt"));
out.write(bytes);
out.close();
}
}
}
}
默认代码页中的输出?总垃圾!
Z:\andrew\projects\sx\1259084>chcp
Active code page: 850
Z:\andrew\projects\sx\1259084>java Foo
== UTF-8
= no bom
ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
= bom
´╗┐ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
== UTF-16LE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
= bom
■A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
== UTF-16BE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
== UTF-32LE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
== UTF-32BE
= no bom
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
= bom
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
但是,如果我们type
保存了文件,该怎么办?它们包含与打印到控制台完全相同的字节。
Z:\andrew\projects\sx\1259084>type *.txt
uc-test-UTF-16BE-bom.txt
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
uc-test-UTF-16BE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣☺↓☺z☺|☺D☺B
R u s s i a n ♦0♦1♦2♦3♦4♦5♦6 ♦M♦N♦O
C J K O`Y}
uc-test-UTF-16LE-bom.txt
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
uc-test-UTF-16LE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
uc-test-UTF-32BE-bom.txt
■ A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
uc-test-UTF-32BE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ☺♣ ☺↓ ☺z ☺| ☺D ☺B
R u s s i a n ♦0 ♦1 ♦2 ♦3 ♦4 ♦5 ♦6 ♦M ♦N
♦O
C J K O` Y}
uc-test-UTF-32LE-bom.txt
A S C I I a b c d e x y z
G e r m a n ä ö ü Ä Ö Ü ß
P o l i s h ą ę ź ż ń ł
R u s s i a n а б в г д е ж э ю я
C J K 你 好
uc-test-UTF-32LE-nobom.txt
A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺ ↓☺ z☺ |☺ D☺ B☺
R u s s i a n 0♦ 1♦ 2♦ 3♦ 4♦ 5♦ 6♦ M♦ N
♦ O♦
C J K `O }Y
uc-test-UTF-8-bom.txt
´╗┐ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
uc-test-UTF-8-nobom.txt
ASCII abcde xyz
German ├ñ├Â├╝ ├ä├û├£ ├ƒ
Polish ąęźżńł
Russian ð░ð▒ð▓ð│ð┤ðÁð ÐìÐÄÐÅ
CJK õ¢áÕÑ¢
的唯一一件事情就是作品UTF-16LE文件,以BOM,打印到通过控制台type
。
如果我们使用除type
打印文件以外的任何其他方法,则会产生垃圾:
Z:\andrew\projects\sx\1259084>copy uc-test-UTF-16LE-bom.txt CON
■A S C I I a b c d e x y z
G e r m a n õ ÷ ³ ─ Í ▄ ▀
P o l i s h ♣☺↓☺z☺|☺D☺B☺
R u s s i a n 0♦1♦2♦3♦4♦5♦6♦ M♦N♦O♦
C J K `O}Y
1 file(s) copied.
根据copy CON
无法正确显示Unicode 的事实,我们可以得出结论,该type
命令具有在文件开头检测UTF-16LE BOM的逻辑,并使用特殊的Windows API进行打印。
我们可以通过cmd.exe
在打开type
文件时打开调试器来看到它:
之后type
打开一个文件时,它检查的BOM 0xFEFF
-即字节
0xFF 0xFE
的小端,如果有这样的BOM,type
设置内部fOutputUnicode
标志。稍后检查此标志以决定是否呼叫WriteConsoleW
。
但这是获取type
Unicode 的唯一方法,并且仅适用于具有BOM表且位于UTF-16LE中的文件。对于所有其他文件,以及没有特殊代码来处理控制台输出的程序,您的文件将根据当前代码页进行解释,并可能显示为乱码。
您可以type
像这样在自己的程序中模拟如何将Unicode输出到控制台:
#include <stdio.h>
#define UNICODE
#include <windows.h>
static LPCSTR lpcsTest =
"ASCII abcde xyz\n"
"German äöü ÄÖÜ ß\n"
"Polish ąęźżńł\n"
"Russian абвгдеж эюя\n"
"CJK 你好\n";
int main() {
int n;
wchar_t buf[1024];
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
n = MultiByteToWideChar(CP_UTF8, 0,
lpcsTest, strlen(lpcsTest),
buf, sizeof(buf));
WriteConsole(hConsole, buf, n, &n, NULL);
return 0;
}
该程序适用于使用默认代码页在Windows控制台上打印Unicode。
对于示例Java程序,尽管通过奇怪的方式弄乱了输出,但我们可以通过手动设置代码页来获得一些正确的输出:
Z:\andrew\projects\sx\1259084>chcp 65001
Active code page: 65001
Z:\andrew\projects\sx\1259084>java Foo
== UTF-8
= no bom
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
ж эюя
CJK 你好
你好
好
�
= bom
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
еж эюя
CJK 你好
你好
好
�
== UTF-16LE
= no bom
A S C I I a b c d e x y z
…
但是,一个设置Unicode UTF-8代码页的C程序:
#include <stdio.h>
#include <windows.h>
int main() {
int c, n;
UINT oldCodePage;
char buf[1024];
oldCodePage = GetConsoleOutputCP();
if (!SetConsoleOutputCP(65001)) {
printf("error\n");
}
freopen("uc-test-UTF-8-nobom.txt", "rb", stdin);
n = fread(buf, sizeof(buf[0]), sizeof(buf), stdin);
fwrite(buf, sizeof(buf[0]), n, stdout);
SetConsoleOutputCP(oldCodePage);
return 0;
}
确实有正确的输出:
Z:\andrew\projects\sx\1259084>.\test
ASCII abcde xyz
German äöü ÄÖÜ ß
Polish ąęźżńł
Russian абвгдеж эюя
CJK 你好
这个故事的主旨?
type
可以打印带有BOM的UTF-16LE文件,而不管当前的代码页如何WriteConsoleW
。chcp
,并且可能仍然会得到奇怪的输出。WriteFile
报告写入的字符数而不是字节数,因此缓冲的编写器会根据非ASCII字符数重试几次“剩余”字节。同样在65001中,在conhost.exe中读取非ASCII字符失败,因为在调用时,每个UTF-16代码均假定1个ANSI字节WideCharToMultiByte
。
GetStdHandle(STD_OUTPUT_HANDLE)
C和C stdout
是控制台句柄。在实践中,要测试控制台,请检查是否GetConsoleMode
成功。另外,不要使用C运行时_isatty
函数来检查低I / O文件描述符是否是控制台。仅检查字符模式设备,其中包括NUL
其他设备。而是_get_osfhandle
直接调用并检查句柄。
类型
chcp
查看您当前的代码页(如Dewfy所说)。
用
nlsinfo
查看所有已安装的代码页,并了解您的代码页号的含义。
您需要安装Windows Server 2003资源工具包(适用于Windows XP)才能使用nlsinfo
。
nlsinfo
不会出现在我的Windows 7存在
nlsinfo
Windows XP SP3计算机上也不存在。
nlsinfo
Windows 10E计算机上也不存在。
Windows代码页问题以及由它们引起的C程序可移植性和本地化问题使我很沮丧。先前的文章详细介绍了这些问题,因此在这方面我将不添加任何内容。
长话短说,最终我最终在Visual C ++标准C库上编写了自己的UTF-8兼容性库层。基本上,此库可确保标准C程序在内部使用UTF-8在任何代码页中正常工作。
这个名为MsvcLibX的库可以在https://github.com/JFLarvoire/SysToolsLib中作为开放源代码获得。主要特点:
GitHub上的MsvcLibX README中有更多详细信息,包括如何构建该库并在您自己的程序中使用它。
上面的GitHub存储库中的发布部分提供了一些使用此MsvcLibX库的程序,这些程序将显示其功能。例:尝试使用我的which.exe工具,在PATH中使用具有非ASCII名称的目录,搜索具有非ASCII名称的程序,并更改代码页。
conv.exe程序是另一个有用的工具。该程序可以轻松地将数据流从任何代码页转换为任何其他代码页。它的默认值是在Windows代码页中输入,在当前控制台代码页中输出。这样可以使用以下简单命令在命令控制台中正确查看Windows GUI应用程序(例如:记事本)生成的数据:type WINFILE.txt | conv
这个MsvcLibX库绝不是完整的,欢迎为改进它而做出的贡献!