Да, это расстраивает - иногда type
и другие программы печатают бред, а иногда нет.
Прежде всего, символы Unicode будут отображаться только в том случае, если текущий шрифт консоли содержит символы . Поэтому используйте шрифт TrueType, например Lucida Console, вместо стандартного шрифта Raster.
Но если консольный шрифт не содержит символа, который вы пытаетесь отобразить, вместо тарабарщины вы увидите вопросительные знаки. Когда вы получаете тарабарщину, происходит больше, чем просто настройки шрифта.
Когда программы используют стандартные функции ввода-вывода C-библиотеки, например printf
, выходная кодировка программы должна совпадать с выходной кодировкой консоли , иначе вы получите бред. chcp
показывает и устанавливает текущую кодовую страницу. Весь вывод, использующий стандартные функции ввода-вывода библиотеки C, обрабатывается так, как если бы он находился в отображаемой кодовой странице chcp
.
Сопоставление выходной кодировки программы с выходной кодировкой консоли можно выполнить двумя различными способами:
Программа может получить текущую кодовую страницу консоли, используя chcp
или
GetConsoleOutputCP
, и сконфигурировать себя для вывода в этой кодировке, или
Вы или программа можете установить текущую кодовую страницу консоли с помощью chcp
или
SetConsoleOutputCP
соответствовать выходной кодировке программы по умолчанию.
Однако программы, использующие Win32 API, могут записывать строки UTF-16LE непосредственно в консоль
WriteConsoleW
. Это единственный способ получить правильный вывод без установки кодовых страниц. И даже при использовании этой функции, если строка не находится в кодировке UTF-16LE для начала, программа Win32 должна передать правильную кодовую страницу
MultiByteToWideChar
. Также WriteConsoleW
не будет работать, если вывод программы перенаправлен; в этом случае нужно больше возиться.
type
работает некоторое время, потому что он проверяет начало каждого файла для метки порядка байтов UTF-16LE (BOM) , то есть байтов 0xFF 0xFE
. Если он находит такую метку, он отображает символы Unicode в файле, используя WriteConsoleW
независимо от текущей кодовой страницы. Но при использовании type
любого файла без спецификации UTF-16LE или при использовании символов, отличных от 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 в начале файла и использования специальных API-интерфейсов Windows для ее печати.
Мы можем увидеть это, открыв cmd.exe
в отладчике, когда он выходит type
из файла:
После того, type
как файл открывается, он проверяет спецификацию - 0xFEFF
т.е. байты с прямым порядком байтов
0xFF 0xFE
- и, если такая спецификация существует, type
устанавливает внутренний fOutputUnicode
флаг. Этот флаг проверяется позже, чтобы решить, стоит ли звонить WriteConsoleW
.
Но это единственный способ получить type
Unicode и только для файлов, которые имеют спецификации и находятся в 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;
}
Эта программа работает для печати Unicode на консоли Windows с использованием кодовой страницы по умолчанию.
Для примера 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
…
Однако, программа на C, которая устанавливает кодовую страницу Unicode UTF-8:
#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
может печатать файлы UTF-16LE с спецификацией независимо от вашей текущей кодовой страницы
- Программы Win32 могут быть запрограммированы для вывода Unicode на консоль, используя
WriteConsoleW
.
- Другие программы, которые устанавливают кодовую страницу и соответственно корректируют свою выходную кодировку, могут печатать Unicode на консоли независимо от того, какой была кодовая страница при запуске программы.
- Со всем остальным вам придется возиться
chcp
, и, вероятно, вы получите странные результаты.