Так, це засмучує: іноді type
й інші програми друкують хитрість, а іноді й ні.
Перш за все, символи Unicode відображатимуться лише у тому випадку, якщо шрифт поточної консолі містить символи . Тому використовуйте шрифт TrueType, наприклад, Lucida Console замість растрового шрифту за замовчуванням.
Але якщо шрифт консолі не містить символу, який ви намагаєтеся відобразити, ви побачите знаки запитання замість гнучкості. Коли ви отримуєте хитрість, відбувається більше, ніж просто налаштування шрифту.
Коли програми використовують стандартні функції вводу / виводу бібліотеки С, як-от printf
, кодування виводу програми повинно відповідати вихідному кодуванню консолі , інакше ви отримаєте хитрість. chcp
показує та встановлює поточну кодову сторінку. Весь вихід із використанням стандартних функцій вводу / виводу бібліотеки С-трактується так, ніби він знаходиться в кодовій сторінці, яку відображаєchcp
.
Зіставлення кодування виводу програми з кодуванням виходу консолі може здійснюватися двома різними способами:
Програма може отримати поточну кодову сторінку консолі, використовуючи chcp
або
GetConsoleOutputCP
, і налаштувати себе на вихід у кодуванні, або
Ви або програма можете встановити поточну кодову сторінку консолі, використовуючи chcp
або
SetConsoleOutputCP
відповідати кодуванню виводу за замовчуванням програми.
Однак програми, що використовують API Win32, можуть записувати рядки UTF-16LE безпосередньо на консоль
WriteConsoleW
. Це єдиний спосіб отримати правильний вихід без встановлення кодових сторінок. І навіть при використанні цієї функції, якщо рядок відсутня в кодуванні UTF-16LE для початку, програма Win32 повинна передати правильну кодову сторінку
MultiByteToWideChar
. Також,WriteConsoleW
не буде працювати, якщо вихід програми буде перенаправлений; в цьому випадку потрібно більше хитрощів.
type
працює деякий час, тому що він перевіряє початок кожного файлу для позначення порядку байтів UTF-16LE (BOM) , тобто байтів 0xFF 0xFE
. Якщо він знаходить таку позначку, він відображає символи Unicode у файлі, використовуючи WriteConsoleW
незалежно від поточної сторінки коду. Але коли ви 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
команда має логіку виявлення BT UTF-16LE на початку файлу та використання спеціальних API для друку Windows.
Це ми можемо побачити, відкривши 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;
}
Ця програма працює для друку 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 з BOM незалежно від поточної кодової сторінки
- Програми Win32 можна запрограмувати для виведення Unicode на консоль, використовуючи
WriteConsoleW
.
- Інші програми, які встановлюють кодову сторінку та відповідно коригують їх кодування, можуть надрукувати Unicode на консолі незалежно від того, якою була сторінка коду при запуску програми
- З усім іншим вам доведеться возитися
chcp
, і, ймовірно, все одно вийде дивний вихід.