Автор: Маркус Кухн (Markus Kuhn)

Джерело: UTF-8 and Unicode FAQ for Unix/Linux

Переклав: Віталій Цибуляк


Цей текст являє собою всебічний інформаційний ресурс з використання Юнікоду/UTF-8 на POSIX-сумісних системах (Лінуксі та Юніксі). Ви знайдете тут і ознайомчу інформацію для користувачів, і докладний довідник для досвідчених розробників.

Юнікод почав заступати ASCII, ISO 8859 та EUC на всіх рівнях. Він дозволяє користувачеві орудувати не тільки будь-якими написами та мовами світу, але й підтримує вичерпний набір математичних і технічних символів, що полегшує обмін науковою інформацією.

Із кодуванням UTF-8, Юнікод може використовуватись у зручний та обернено-сумісний спосіб у середовищах, розроблених цілком і повністю навколо ASCII, як Юнікс. UTF-8 - це шлях, через який Юнікод знаходить своє застосування в Юніксі, Лінуксі та подібних системах. А тепер час переконатися, що ви добре знайомі з ним, і ваше програмне забезпечення належно його підтримує.

Що таке UCS та ISO 10646?

Інтернаціональний стандарт ISO 10646 дає визначення Універсальному Набору Символів (UCS). UCS - це надмножина всіх інших стандартів наборів символів. Він гарантує сумісність в обох напрямках з рештою наборами символів. Це означає, що ніяка частина інформації не втрачається при перетворенні будь-якого текстового ланцюжка в UCS і назад в оригінальне кодування.

UCS містить символи, потрібні для представлення практично всіх відомих мов. Це стосується не тільки латинських, Грецьких, Кириличних, Гебрайських, Арабських, Вірменських та Грузинських письмен, але й Китайського, Японського, Корейських ідеографів Хан, так само як таких письмен, як Хірагана, Катакана, Хангул, Деванагарі, Бенгалі, Гурмукхі, Гужараті, Орія, Таміл, Телугу, Каннада, Малаялам, Тай, Лао, Кхумер, Бопомофо, Тібетського, Рунічного, Ефіопського, Канадських складів, Чероке, Монгольських, Огхам, Мянмар, Сінгала, Тана, Їі та інших. Для ще не освоєних написів усе ще ведуться розслідування щодо того, як найкраще їх закодувати для використання в комп'ютерах. Це стосується не тільки історичних письмен, таки як Кунеїформ, ієрогліфів та різних Індо-Європейських позначень, але й деяких мистецьких письмен, як от Толкієнас Тенгвар та Кірф. UCS також включає велике число графічних, типографічних, математичних та наукових символів разом з тими, які надані TeX, PostScript, APL, Інтернаціональним Фонетичним Алфавітом (IPA), MS-DOS, MS-Windows, Macintosh, шрифтами OCR, а також чисельними системами опрацьовування тексту та видавництва. Стандарт продовжує розвиватися та оновлюватися. У майбутньому можна очікувати іще май екзотичних та спеціалізованих символів.

ISO 10646 одразу визначив 31-бітний набір символів. Підмножини 216 символів, в якому елементи відрізняються (32-бітному цілочисельному представленні) тільки 16-а молодшими бітами, називаються рівнями в UCS.

Найуживаніші символи, включаючи ті, які можна знайти в основних стандартах кодування, розміщено на першому рівні (значення 0x0000 до 0xFFFD), який називається Основним Багатомовним Рівнем (BMP), або Рівнем 0. Пізніші символи, додані поза межами 16-бітного BMP, призначені головно для спеціального застосування, як от для передання історичних письмен та наукових позначень. Сьогоднішні плани передбачають, що поза 21-бітним кодовим простором від 0x000000 до 0x10FFFF, який охоплює понад мільйон майбутніх символів, символи призначатися не будуть. Стандарт ISO 10646-1 вперше було опубліковано в 1993 році, і в ньому описана архітектура набору символів і вміст BMP. Другу частину, ISO 10646-2, було додано в 2001-у році, і в ній означено символи, закодовані зовні BMP. У виданні 2003-го року, обидві частини було зведено в єдиний стандарт ISO 10646 (сто шість сорок шість). Нові символи і надалі додаються, але вже наявні не зміняться і вважаються сталими.

UCS присвоює кожному символові не тільки номер коду, але й офіційну назву. Шістнадцяткове число, яке представляє значення UCS або Юнікоду, звично позначається суфіксом U+, як от U+0041 для латинської літери A верхнього регістру. Символи UCS U+0000 до U+007F - тотожні US-ASCII (ISO 646 IRV), а від U+0000 до U+00FF - тотожні ISO 8859-1 (Latin-1). Діапазони від U+E000 до U+F8FF, а також більші, які знаходяться поза BMP, зарезервовано для приватного використання. UCS також визначає декілька метод кодування символьних ланцюжків як послідовностей байтів, як то UTF-8 і UTF-16.

Повну довідку із стандарту UCS можна отримати зі стандарту ISO/IEC 10646

Стандарт можна замовити через Інтернет від ISO як набір файлів у форматі PDF на компактному диску за 112 CHF (?).

У версесні 2006-го року, ISO випустила вільну копію PDF ISO10646:2003 на її веб-сторінці Вільних стандатів. Архівований ZIP файл складає 82 Мб.

Що таке сполучні символи?

Деякі коди UCS було призначено сполучним символам. Вони подібні до безпростірних клавіш наголосу на друкарських машинах. Сполучні символи не утворюють повного символу самі по собі. Це - наголос, або інше діактричне позначення, яке додається до попереднього символу. Таким чином можна додати будь-який діакричний знак до будь-якого символу. Найважливіші виділені символи, як ті, які використовуються в орфографіях поширених мов, мають власні коди в UCS, щоб забезпечити зворотню сумісність зі старшими наборами символів. Вони відомі, як попередньо-скомпоновані символи. Попередньо-скомпоновані символи наявні в UCS для сумісності зі старшими кодуваннями, які не мають сполучних символів. Механіз сполучення (комбінування) символів дозволяє додавати наголос та інші діактричні позначення до будь-яких знаків. Особливо важливим це є в наукових позначеннях, таких як математичних формулах та Інтернаціональному Фонетичному алфавіті, де може з'явитись потреба в будь-якій комбінації основного символу та одного чи більше діактричних позначень.

Сполучні символи слідують за тими, які вони модифікують. Так наприклад німецький знак "умлаут" A (Латинська заголовна A з діарезисом) можна представити як попередньо-скомпонований код UCS U+00C4, або як комбінацію звичайної "Латинської заголовної літери A" з наступним сполучним діарезисом: U+0041 U+0308. Можна також застосувати декілька сполучних символів, якщо треба, щоб накласти чисельні наголоси чи додати сполучні позначення одночасно над і під основним символом. Напис Тай, наприклад, вимагає до двох сполучних символів з одним основним.

Які є рівні втілення UCS?

Не кожна система здатна підтримувати всі складні механізми UCS, як от сполучні символи. Тому ISO 10646 зазначає три наступних рівня реалізації:

Рівень 1 : Сполучні символи і символи Гангул Джамо не підтримуються. (Гангул Джамо - це альтернативне представлення попередньо-скомпонованих складів Гангул як речень з голосних і приголосних. Вони потрибні для повної підтримки корейських письмен, Середньокорейського включно.)

Рівень 2 : Подібний до Рівня 1, але з доданням деяких письмен і обмеженого списку сполучних символів (для гебрайської, арабської, девангарської, бенгальської, гурмукської, орійської, тамільської, телугської, каннадської, малаяламської, тайської та лаоської мов). Ци письмена неможливо належно представити в UCS без підтримки бодай деяких сполучних символів.

Рівень 3 : Підтримуються всі символи UCS так, що математики, наприклад, можуть додати тильду чи стрілку (чи обидва знака) до будь-якого символу.

Чи прийнято UCS за національні стандарти?

Так, багато країн оприлюднило національні адоптації ISO 10646, подеколи після додання певних нотаток з перехресним посиланням на старші національні стандарти, настанов щодо втілення, і специфікацій різноманітних національних підмножин:

Китай : GB 13000.1-93

Японія : JIS X 0221-1:2001

Корея : KS X 1005-1:1995 (Включає ISO 10646-1:1993 з поправками 1-7)

В'єтнам : TCVN 6909:2001 (Закодований 16-бітами в'єтнамський набір символів як мала підмножина UCS для використання державними установами, станом на 01.07.2002.)

Іран : ISIRI 6219:2002, Інформаційна технологія: Перський механізм обміну та відображення даних з використанням Юнікоду. (Не являється версією чи підмножиною ISO 10646, а окремим документом з додатковими національними вказівками та роз'ясненнями щодо обробки перської мови та арабських письмен в Юнікоді.)

Що таке Юнікод?

Наприкінці 1980-их відбулися дві окремі спроби утворення єдиного уніфікованого набору символів. Одна з них походила від проекту ISO 10646 Інтернаціональної Організації із Стандартизації (ISO), а інша - від проекту Юнікод, організованого консорціумом (на початку переважно Американських) виробників багатомовних програмних засобів. На щастя, учасники обох проектів збагнули десь у 1991-у році, що два відмінних уніфікованих наборів символів - це не те, що потрібно Світові. Вони об'єднали свої зусилля у створенні єдиної кодової таблиці. Обидва проекти все ще існують і публікують відповідні стандарти незалежно, проте Кнсорціум Юнікод й ISO/IEC JTC1/SC2 погодились зберегти сумісність кодових таблиць Юнікоду та ISO 10646, і надалі тісно координувати будь-які подальші розширення. Unicode 1.1 відповідає ISO 10646-1:1993, Unicode 3.0 - ISO 10646-1:2000, Unicode 3.2 включає ISO 10646-2:2001, Unicode 4.0 - ISO 10646:2003, а Unicode 5.0 відповідає ISO 10646:2003 плюс поправки 1-3. Усі додання до Юнікоду, починаючи з 2.0 - сумісні, і додаватися будуть тільки нові символи; у майбутньому, ні одного з наявних символів не буде усунуто чи перейменовано.

Стандарт Юнікод можна замовити як звичайну книжку, наприклад через amazon.com за приблизно 60 USD.

Якщо ви часто працюєте з обробкою тексту та наборами символів, то вам безумовно слід придбати примірник. Unicode 5.0 також доступний в мережі.

Яка ж різниця між Юнікодом та ISO 10646?

Стандарт Юнікод, опублікований Консорціумом Юнікод, відповідає ISO 10646, 3-го рівня втіленння. Усі символи розміщено в тих самих положеннях і мають ті самі назви в обох стандартах.

Стандарт Юнікод визначає крім цього набагато більше семантики, пов'язаної з деякими символами і служить загалом кращим путівником для розробників високоякісних типографських видавничих систем. В Юнікоді описані алгоритми для відтворення форм представлення деяких письмен (наприклад арабського), обробки двонапрямних текстів, при змішанні скажімо ларинського та гебрайського, алгоритми сортування та порівняння ланцюжків і багато більше.

Стандарт ISO 10646 з іншого боку - це ніщо більше, як проста таблиця набору символів з порівняннями зі старими стандартами ISO 8859. Він означує певну термінологію, пов'язану зі стандартом, визначає деякі альтернативи кодування, і містить специфікації щодо використання UCS стосовно інших уставлених стандартів ISO, як от ISO 6429 та ISO 2022. Існують також й інші тісно пов'язані з цим стандати, зокрема ISO 14651, який стосується сорування ланцюжків UCS. Гарною рисою стандарту ISO 10646-1 є те, що він пропонує приклади гліфів CJK у п'ятьох стильових варіантах, тоді як Юнікод показує ідеографи (ієрогліфи) CJK тільки в китайському.

Що таке UTF-8?

UCS та Юнікод - це в першу чергу таблиці кодів, які призначають цілі числа символам. Проте існує декілька альтернатив стосовно того, як послідовності таких символів і їхні відповідні цілі значення можна представити, як послідовності байтів. Два найпростіших кодування зберігають тексти Юнікоду як послідовності 2- або 4-ох байтових одиниць. Офіційними назвами цих кодувань є UCS-2 та UCS-4, відповідно. Окрім коли вказано інакше, старший байт іде першим у них. Файл ASCII або Latin-1 можна перетворити на UCS-2 просто, якщо додати байт 0x00 попереду кожного байта ASCII. Якщо нам потрібен файл UCS-4, тоді слід додати три байти 0x00 перед кожним байтом ASCII натомість.

Використання UCS-2 (або UCS-4) в Юніксі призвело би до дуже серйозних проблем. Ланцюжки в цих кодуваннях можуть містити, як складові багатьох широких символів, такі байти, як "\0" або "/", які мають особливе значення в назвах файлів та інших параметрах функцій бібліотек C. Крім цього, більшість знарядь Юнікса очікують файли ASCII, і не можуть читати 16-бітні (або 32-бітні) слова та символи без суттєвих змін. З цих міркувань, UCS-2 це придатне, як зовнішнє кодування для Юнікоду, в назвах файлів, текстових файлах, змінних оточення тощо.

Кодуванню UTF-8, визначеному в ISO 10646-1:2000 Додатку D, а також описаному в RFC 3629 так само, як і в розділі 3.9 стандарту Unicode 4.0, не притаманні ці пробеми. Це - явно шлях, яким треба йти при використанні Юнікоду в Юнікс-подібних системах.

UTF-8 характерні наступні властивості:

  • Символи UCS U+0000 до U+007F (які відповідають ASCII) закодовано просто як 0x00 по 0x7F (заради сумісності з ASCII). Це означає, що файли та ланцюжки, які містять тільки 7-бітні символи ASCII, мають те саме кодування як в ASCII, так і в UTF-8.
  • Усі символи UCS, більші за U+007F, закодовано як послідовність із декількох байтів, кожен з яких має заданим найзначущий біт. Таким чином, ні один байт ASCII (0x00-0x7F) не може з'явитися, як частина якогось іншого символу.
  • Перший байт багатобайтового ряду, який представляє не-ASCII символ, завжди знаходиться в межах від 0xC0 до 0xFD, і вказує на те, скільки байтів, які за ним слідують, належать цьому символу. Решта байтів багатобайтового ряду знаходяться в межах від 0x80 до 0xBF. Це дає можливість легкої ресинхронізації, і робить кодування станонезалежним та стійким у разі втрати байтів.
  • Ним можна закодувати всі можливі 231 значення UCS.
  • Символи, закодовані в UTF-8 можуть теоретично займати до шести байтів, проте 16-бітні символи BMP не бувають більші за три байта.
  • Порядок сортування ланцюжків UCS-4 із найбільш значущим байтом першим - збережено.
  • Байти 0xFE та 0xFF ніколи не використовуються в кодуванні UTF-8.

Для представлення символу використовуються послідовності байтів, наведені нижче. Ряд, який буде застосовано, залежить від юнікодового значення символу.

U-00000000 - U-0000007F: 0xxxxxxx
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

Положення розрядів, позначених як xxx, заповнюються бітами коду символу. Крайній правий біт x являється найменш значущим бітом. Використовуватись може тільки найкоротша багатобайтова послідовність, здатна представити номер коду символу. Замітьте, що в багатобайтовому ряді, число одиничних бітів попереду в першому байті дорівнює загальному числу байтів усієї послідовності.

Приклад: символ Юнікоду U+00A9 = 1010 1001 (знак авторського права) закодовано в UTF-8, як

11000010 10101001 = 0xC2 0xA9

а симол U+2260 = 0010 0010 0110 0000 (знай нерівності) - як

11100010 10001001 10100000 = 0xE2 0x89 0xA0

Офіційна назва цього формату становить UTF-8, де UTF означає UCS Transformation Format (формат перетворення UCS). Будь ласка, не посилайтеся в документації чи деінде на UTF-8 якось інакше (наприклад як utf8 або UTF_8), хіба що, звичайно, ви вказуєте назву якоїсь змінної, а не самого кодування.

Важлива нотатка для розробників функцій розкодовування UTF-8: з міркувань безпеки, дешифратор UTF-8 не повинен приймати послідовності UTF-8, довші ніж треба, щоб закодувати символ. Наприклад символ U+000A (переведення рядка) слід розуміти в потоці UTF-8 тільки як 0x0A, а не жодну іншу з наступних довгих форм:

0xC0 0x8A
0xE0 0x80 0x8A
0xF0 0x80 0x80 0x8A
0xF8 0x80 0x80 0x80 0x8A
0xFC 0x80 0x80 0x80 0x80 0x8A

Занадто довгі послідовності UTF-8 можна використати із злим умислом, щоб обійти перевірку підланцюжків UTF-8, яка шукає найкоротшу з можливих форм кодування. Усі занадто довгі послідовності UTF-8 починаються з одного з наступних рядів байтів:

1100000x (10xxxxxx)
11100000 100xxxxx (10xxxxxx)
11110000 1000xxxx (10xxxxxx 10xxxxxx)
11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx)
11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx)

Також зауважте, що положення в коді від U+D800 до U+DFFF (сурогати UTF-16) так само як U+FFFE до U+FFFF не повинні з'являтися у звичайних даних UTF-8 або UCS-4. Дешифратори UTF-8 мають розглядати їх, як неправильно сформовані чи задовгі ряди заради безпеки.

Файл випробування дешифратора UTF-8 Маркуса Кухна містить систематизовану підбірку погано сформатованих та задовгих послідовностей UTF-8, що дозволить вам перевірити надійність вашого дешифратора.

Хто винайшов UTF-8?

Кодування, відоме сьогодні як UTF-8, було винайдено Кеном Томсоном. Воно народилося у вечірні часи 2-го вересня 1992-го року в одній з кав'ярень Нью Джерсі, де він розробив її в присутності Роберта Пайка на серветці (дивіться "Історію UTF-8" Роберта Пайка). Воно замістило ранні спроби розробок FSS/UTF (формату перетворення UCS, безпечного для файлової системи), які циркулювали в робочих документах X/Open в серпні 1992-року, укладених Гарі Міллером (IBM), Грегером Лейджонхафвудом та Джоном Ентенманом (SMI), як спроба заміни важкого в обчисленні кодування UTF-1 з першого видання ISO 10646-1. Наприкінці першого ж тижня у вересні 1992-го року, Пайк і Томсон перетворили Plan 9 від Лабораторій Белл AT&T у першу в світі операційну систему з використанням UTF-8. Вони звітували про свій дослід на Технічній Конференції USENIX взимку 1993-го, і на засіданні в Сан Дієго 25-29-го січня 1993-го року, стор. 43-50. FSS/UTF якийсь час ще називали UTF-2, доки пізніше не поміняли назву на UTF-8 і пустили в процес стандартизації Об'єднаною Групою Стандартизації X/Open XOJIG.

Де можна знайти хороші файли з прикладами UTF-8?

Ось декілька цікавих файлів з прикладами UTF-8 для перевірки та демонстрації:

Які існують кодування?

Обидва стандарти, UCS та Юнікод, - це перш за все великі таблиці, які присвоюють кожному символу цілочисельне значення. Якщо ви вживаєте термін 'UCS', 'ISO 10646' або Юнікод, то це стосується тільки перетворення між символами та цілими. Воно нічого не каже про те, як зберігати ці цілі, як послідовність байтів у пам'яті.

ISO 10646-1 описує кодування UCS-2 та UCS-4. Вони означають послідовності з 2 і 4 байтів на символ, відповідно. ISO 10646 із самого початку було означено, як 31-бітний набір символів (з можливими значеннями в діапазоні від U-00000000 до U-7FFFFFFF), проте перший символ поза межами Базового Багатомовного Рівня (BMP) було призначено не раніше, ніж у 2001-у році, тобто призначено символ вище за 216 положення в таблиці (дивіться 10646-2 та Unicode 3.1). UCS-4 здатне представити всі символи Юнікоду та UCS, UCS-2 може представити тільки ті, які належать BMP (U+0000 до U+FFFF).

Юнікод напочатках припускався, що кодування буде складатися з UCS-2, і не передбачав жодних положень для символів зовні BMP (U+0000 до U+FFFF). Коли ж стало зрозуміло, що для певних спеціальних застосувань (історичні алфавіти та ієрогліфи, математичні та музичні позначення тощо) потрібно більше ніж 64 кілобайти символів, тоді Юнікод перетворився на своєрідний 21-бітний набір символів можливими положеннями коду в діапазоні від U-00000000 до U-0010FFFF. До BMP було також додано сурогатні символи 2A1024 (U+D800 to U+DFFF), щоб не-BMP символи 1024A1024 можна було представити, як ряд із двох 16-бітних сурогатних символа. Так народилося UTF-16, яке представляло розширений 21-бітний Юнікод якоюсь мірою зворотньо-сумісний з UCS-2.

---слід закінчити---

Які мови програмування підтримують Юнікод?

Новітніші програми, розроблені, починаючи з 1993-го року, вже мають спеціальний тип даних для символів 10646-1 Юнікоду. Це стосується Ada95, Java, TCL, Perl, Python, C# та інших.

ISO C 90 визначає механізми для обробки багатобайтових та широких символів. Ці засоби було покращено Поравкою 1 ISO C 90 в 1994-у році, і навіть більше всосконалень було привнесено стандартом ISO C 99. Дані засоби було розроблено першочергово для східноазійських кодувань. З odnogo боку, вони дещо заскладні для опрацьовування UCS (роботи з "shift-послідовностями"), з іншого - їм бракує підтримки більш розвинених аспектів UCS (сполучних символів тощо). UTF-8 являється прикладом того, що стандарт ISO C називає багатобайтовим кодуванням. Для утримування символів унікоду може використовується тип wchar_t, який у сучасних середовищах зазвичай відповідає 32-бітному цілому зі знаком.

На жаль, тип wchar_t вже широко застосовувався для різних азійських 16-бітних кодувань у 1990-их. Тому стандарт ISO C 99 було зв'язано (обмежено) зворотньою сумісністю. Поміняти цей тип, щоб він міг працювати з UCS, як у Java й Ada95, було неможливо. Проте, компілятор C міг принаймні сигналізувати додаток, що wchar_t гарантовано утримуватиме значення UCS у всіх локалях. Для цього, для нього було визначено макрос __STDC_ISO_10646__, який складається з цілочисельної сталої, яка має форму ррррммL. Рік і місяць відповідають версії ISO/IEC 10646 та поправкам, які було втілено. Наприклад, __STDC_ISO_10646__ == 200009L, якщо реалізація підтримує ISO/IEC 10646-1:2000.

Як слід користуватися Юнікодом у Лінуксі?

До появи UTF-8, користувачі Лінакса навколо світу повинні були послуговуватися різними мовними розширеннями ASCII. Найпоширенішими були ISO 8859-1 та ISO 8859-2 в Європі, ISO 8859-7 у Греції, KOI8 / ISO 8859-5 / CP1251 у Росії, EUC та Shift-JIS в Японії, BIG5 у Тайвані тощо. Це ускладнювало обмін файлами, і прикладні програми повинні були дбати про різні малі відмінності між цими кодуваннями. Підтримка цих кодувань була зазвичий неповною, недосить тестованою та незадовільною, оскільки розробники програм рідко коли самі користувалися цими кодуваннями.

Через ці складнощі основні розповсюджувачі Лінукса та розробники тепер відкидають ці старі кодування на користь UTF-8. Підтримка UTF-8 суттєво зросла за останні декілька років, і багато людей тепер користуються UTF-8 повсякденно в

  • текстових файлах (джерельних кодах, файлах HTML, поштових повідомленнях тощо)
  • назвах файлів
  • стандартному вводі та стандартному виводі, конвеєрах
  • змінних оточення
  • буферах вирізки та вставляння
  • телнеті, модемах та сполученнях з емуляторами терміналів через послідовний порт

та інших областях, де раніше послідовності байтів розглядалися як ASCII.

У режимі UTF-8, такі емулятори терміналів, як xterm або рушій консолі Лінукса перетворюють кожний натиск клавіші на відповідну послідовність UTF-8, і надсилають її на стандартний ввід процесу переднього плану. Аналогічно, вивід процесу на стандартний потік виводу надсилається емуляторові терміналу, де його буде оброблено дешифратором UTF-8, а потім відображено з використанням 16-бітного шрифту.

Повного функціонування Юнікоду в усьому його блиску (тобто високоякісному верстанні арабських та індійських письмен) можна очікувати тільки від витончених багатомовних програмних пакетів з обробки тексту. Те, що Лінукс підтримує сьогодні в широкому плані, це щось набагато простіше, спрямоване головним чином на заміну старих 8- та 16-бітних наборів символів. Емулятори термінала Лінукса та знаряддя командного рядка зазвичай підтримують тільки 1-ий рівень втілення ISO 10646-1 (без сполучних символів), і тільки такі письмена, як Латинське, Грецьке, Кириличне, Вірменське, Грузинське, CJK (Китайське, Японське, Корейське) і багато наукових символів, не потребують подальшої обробки. На цьому рівні, підтримку UCS можна порівняти з підстримкою ISO 8859 з тією істотною різницею, що ми тепер маємо тисячі символів у нашому розпорядженні, символи може бути представлено багатобайтовими послідовностями, і що ідеографічні китайські/японські/корейські символи вимагають двох клітин термінала замість однієї (являються подвійної ширини).

Підтримка 2-го рівня у формі сполучних символів для певних письмен (зокрема Тайського) і Гангул Джамо почасти теж наявна (тобто деякі шрифти, емулятори терміналів і редактори підтримують це через мехамізм простого накладення ланцюжків), але перевагу слід надати попередньо-скомпонованим послідовностям там, де це можливо. А більш формальною мовою: переважним способом закодовування тексту в Юнікод у Лінуксі має бути "Форма нормалізації мови C", як описано в "Технічному звіті про Юнікод #15".

Постачальних однієї впливової операційної системи, несумісної з POSIX, (яку ми не будемо згадувати тут) виступив ідеєю, щоб всі файли з Юнікодом починалися з символу ZERO WIDTH NOBREAK SPACE (безпростірного пробілу нульової довжини) U+FEFF, який також позначав би сигнатуру порядку байтів (BOM - byte-order mark) для розпізнання кодування та порядку байтів, які використовуються у файлі. Лінукс/Юнікс не користуються жодними BOM та сигнатурами. Вони би порушили забагато умовностей синтаксису ASCII, які вже існують (як от сценарії, які починаються з #!). На системах POSIX, обрана локаль і так ідентифікує очікуване кодування у файлах вводу та виводу процесу. Також прозвучала пропозиція називати файли UTF-8 без сигнатури файлами UTF-8N, але цей нестандартний термін звичайно не використовується у світі POSIX.

Перш ніж перейти на UTF-8 у Лінуксі, оновіть свою інсталяцію новітнім дистрибутивом із найсучаснішою підтримкою UTF-8. Це особливо актуально, якщо ви користуєтесь якимось дистрибутивом, старшим за SuSE 9.1 або Red Hat 8.0. До них, підтримка UTF-8 не була ще досить зрілою для повсякденного використання.

Red Hat 8.0 (у вересні 2002-го року) був першим дистрибутивом Лінукса, який перейшов на UTF-8, як стандартне кодування для більшості локалей. Єдиним винятком служили китайська/японська/корейська локалі, для яких на той час існувало ще забагато знарядь, які ще не підтримували UTF-8. Це перше масове впровадження UTF-8 у Лінуксі спричинило до досить швидкого вирішення більшості ще відкритих питань у 2003-у році. Відтак (у квітні 2004-го) на UTF-8 перейшов дистрибутив SuSE 9.1. За ним послідував Ubuntu Linux, перший похідний від Debian дистрибутив, який прийняв UTF-8 за стандартну локаль. З міграцією трьох найпопулярних дистрибутивів, пов'язані з UTF-8 помилки було тепер полагоджено у практично всіх добре утримуваних знаряддях Лінукса. Очікується, що й інші дистрибутиви послідують за ними.

Як мені слід поміняти програми?

Якщо ви розробник, то існує декілька підходів що щодо вирішення питання підтримки UTF-8. Ми можемо розділити їх на дві категорії, які я назву часковими та цікловитими перетвореннями. При частковому перетворенні, дані зберігаються скрізь у формі UTF-8, і вимагається лише незначних змін у програмному забезпеченні. При цілковитому перетворенні, будь-які дані UTF-8, прочитані програмою, обернено на широкосимвольні масиви і скрізь у програмі обробляються відповідно. Ланцюжки буде перетворено назад в UTF-8 тільки під час виводу. Внутрішньо, символ залишається об'єктом пам'яті сталого розміру.

Ми також можемо відокремити жорсткий та локалезалежний підхід до підтримки UTF-8, залежно від того, наскільки обробка ланцюжків покладається на стандартну бібліотеку. C пропонує цілий ряд функцій з обробки ланцюжків, спроектованих для оперування довільними локалезалежними багатобайтовими кодуваннями. Прикладний програміст, який повністю покладається на них, може навіть і не знати усіх тонкощів, пов'язаних з кодуванням UTF-8. Існує велика ймовірність, що просто зміна локалі автоматично ввімкне підтримку декількох інших багатобайтових кодувань (скажімо EUC). Інший спосіб - це жорстко закодувати знання про UTF-8 у додаток. Це може призвести у деяких випадках до суттєвого поліпшення швидкодії. Це - можливо найкращий вихід для додатків, які використовуватимуться тільки з ASCII та UTF-8.

Навіть там, де є бажаною підтримка усіх багатобайтових кодувань, підтримуваних libc, можливо варто додати код, оптимізований під UTF-8. Завдяки самостійній синхронізації кодування UTF-8, воно може оброблятися дуже швидко. Локалезалежні ланцюжкові функції libc можуть зайняти в два рази більше часу, ніж еквіваленті жорстко вбудовані функції UTF-8. Поганим прикладом для наслідування була grep 2.5.1 GNU, яка цілком покладалася на локалезалежні функції libc, як mbrlen(), для загальної підтримки багатобайтових кодувань. Це робило її у 100 разів повільнішою у багатобайтовому режимі порівняно з однобайтовим! Інші прикладні програми з вбудованою підтримкою регулярних виразів у кодуванні UTF-8 (наприклад Perl 5.8) не потерпають від такого разючого вповільнення.

Більшість додатків працюють дуже добре і з частковим перетворенням. Саме це робить додання підтримки UTF-8 здійсненним в Юніксі взагалі. Так наприклад такі програми, як cat і echo не треба міняти зовсім. Вони можуть навіть і не знати про те, чи їхній ввід та вивід складається з ISO 8859-2 чи UTF-8, оскільки оперують тільки потоками байтів без їхньої обробки. Вони розпізнають тільки символи ASCII та керівні послідовності на кшталт '\n', які ніяким чином не змінюються в UTF-8. Тому кодування та розкодовування з UTF-8 здійснюється для таких додатків повністю в емуляторі термінала.

Для будь-якої програми, яка визначає кількість символів у лацюжку, перераховуючи байти, потрібно внести декілька невеликих змін. З UTF-8, так само як з іншими багатобайтовими кодуваннями, де важливою є довжина текстового ланцюжка, програмісти мають чітко розрізняти між

  1. числом байтів,
  2. числом символів,
  3. шириною екрана (тобто число клітин положень курсора в емуляторах термінала VT100)

ланцюжка.

Функція C strlen() завжди лічить кількість байтів. Це число, потрібне скажімо для керування пам'яттю (визначення величини буферу ланцюжків). Коли вивід strlen використовується для цього, тоді нічого міняти не треба.

Кількість символів можна полічити в C портабельно з використанням mbstowcs(NULL,s,0). Це діє з UTF-8 так само, як і з будь-яким іншим підтримуваним кодуванням, за умови, що обрано відповідну локаль. Вбудованою технікою полічити кількість символів у ланцюжку UTF-8 є: рахувати всі байти окрім тих, що знаходяться в діапазоні 0x80 - 0xBF, оскільки ці байти просто являються заповнювачами, а не самими символами. Однак потреба рахувати символи виникає на диво рідко в прикладних програмах.

У додатках, написаних для ASCII або ISO 8859 набагато частіше можна зустріти використання strlen, щоб передбачити число стовпчиків, на які переміститься курсор у терміналі у разі виводу ланцюжка. З UTF-8, ні відлік байтів, ні відлік символів не може передбачити ширину виводу, оскільки ідеографічні символи (китайські, японські, корейські) займають два стовчика (клітинки), тоді як керівні та сполучні - жодного. Для визначення ширини ланцюжка на екрані термінала, потрібно розкодувати послідовність UTF-8, а потім скористатися з функції wcwidth для перевірки ширини виводу кожного символу, або wcswidth для виміру цілого ланцюжка.

Так наприклад, програму ls треба поміняти, оскільки, не знаючи ширини назв файлів, вона не може правильно сформувати таблицю для виводу назв файлів і каталогів користувачеві. Так само інші програми, які припускають, що вивід буде відображено шрифтом сталої довжини та форматують його відповідно, повинні навчитися рахувати стовпчики в тексті UTF-8. Такі функції редагування, як видалення єдиного символу слід трохи поміняти, щоб вони усували всі байти, які можуть належати символові. Це вплинуло зокрема на редактори (vi, emacs, readline тощо) так само, як і на програми з використанням бібліотеки ncurses.

Будь-яке Юнікс-подібне ядро може обійтися гнучким перетворенням і потребує тільки кількох незначних змін для повної підтримки UTF-8. На більшість функцій ядра з обробки ланцюжків (назв файлів, змінних оточення) кодування не впливає ніяк. У Лінуксі, зміни були потрібні в наступних місцях:

  • Відображення в консолі та рушій клавіатури (ще один емулятор VT100) повинні закодовувати та розкодовувати UTF-8 і підтримувати принаймні якусь підмножину набору символів Юнікоду. Починаючи з ядра версії 1.2 Лінукса, така підтримка вже існувала (для ввімкнення режиму UTF-8 треба надіслати ESC %G консолі).
  • Рушії зовнішніх файлових систем, таких як VFAT та WinNT, повинні перетворювати кодування символів назв файлів. UTF-8 тут являється однією з опцій кодування, і команда mount повинна вказати рушію ядра, що користувацький процес має розглядати назви назви файлів, закодовані в UTF-8. Оскільки VFAT та WinNT користуються Юнікодом, то UTF-8 - це єдине кодування, яке гарантує перетворення наз файлів без втрати символів.
  • Рушій термінала будь-якої системи, підпорядкованої POSIX, підтримує "оброблений" (cooked) режим, в якому наявна рудиментарна функціональність редагування рядка. Для того, щоб фунція стирання попереднього символу (erase) (яку активовано, коли ви натискаєте клавішу Backspace) працювала правильно з UTF-8, треба їй вказати не рахувати байти-заповнювачі з діапазому 0x80-0xBF як повноцінні символи, а видаляти їх як частина багатобайтової послідовності UTF-8. Оскільки ядро не обізнане з механізмом локалі libc, то має існувати інший механізм для вказівки рушієві термінала, що використувується UTF-8. Версії ядра Лінукса 2.6 і вище підтримують біт прапорця IUTF8 члена c_iflag структури termios. Якщо його задано, то редагування рядка в "обробленому" режимі розглядатиме багатобайтові послідовності UTF-8 правильно. Цей режим можна ввімкнути в оболонці командою "stty iutf8". Xterm і подібні мають задати цей біт автоматично, коли їх викликано в локалі UTF-8.

Підтримка Юнікоду та UTF-8 мовою C

Починаючи з glibc 2.2 GNU, тип wchar_t офіційно призначено для використання з 32-бітними значеннями ISO 10646, незалежно від поточної локалі. Це сигналізується означенням макросу __STDC_ISO_10646__ в додатках, як того вимагає ISO C99. Функції ISO C багатобайтового перетворення (mbsrtowcs(), wcsrtombs() тощо) повністю втілено в glibc 2.2 і вище, і можуть використовуватись для конвертації між wchar_t та будь-яким локалезалежним багатобайтовим кодуванням з UTF-8, ISO 8859-1 і так далі, включно.

Наприклад, ви можете написати:

#include <stdio.h>
#include <locale.h>
   
int main()
{
    if (!setlocale(LC_CTYPE, "")) {
        fprintf(stderr, "Can't set the specified locale! "
                "Check LANG, LC_CTYPE, LC_ALL.\n");
        return 1;
    }
    printf("%ls\n", L"SchAP:ne GrA 1/4Ae");
    return 0;
}

Викличте цю програму з параметрами локалі LANG=de_DE, і вивід здійснюватиметься в ISO 8859-1. Викличте її з LANG=de_DE.UTF-8, і вивід буде в UTF-8. Вказівник формату %ls у printf викликає wcsrtombs для того, щоб перетворити широкосимвольний ланцюжковий аргумент у локалезалежне кодування байтів.

Багато функцій C не залежать від локалі, і шукають тільки послідовності байтів з нульовим закінченням:

strcpy strncpy strcat strncat strcmp strncmp strdup strchr strrchr
strcspn strspn strpbrk strstr strtok

Деякі з них (наприклад strcpy) можуть на рівних використовуватись як для однобайтових (ISO 8859-1), так і для багатобайтових (UTF-8) кодувань наборів символів, оскільки не мають жодної потреби знати, наскільки довгим є символ, тоді як інші (наприклад strchr) і залежать від символу, який буде відображено, як єдине значення char, і являються менш корисними в UTF-8 (strchr діятиме добре тільки, коли ви шукатимете символ ASCII в ланцюжкові UTF-8).

Інші функції C залежать від локалі, але так само добре працюють в локалях UTF-8:

strcoll strxfrm

Як активувати режим UTF-8?

Якщо у вашому додатку застосовано часткове перетворення, і він не користується стандартними локалежалежними багатобайтовими функціями C (mbsrtowcs(), wcsrtombs() тощо) для обернення всього у wchar_t для обробки, тоді ви маєте знайти якийсь спосіб виявляти, чи текстові дані складаються з якогось 8-бітного кодування (як ISO 8859-1, де 1 байт = 1-у символу), чи з UTF-8. Щойно всі на світі почнуть користуватися тільки UTF-8, ви можете зробити його стандартним кодуванням, а доти можливо доведеться підтримувати і класичні 8-бітні набори, і UTF-8.

Перша хвиля прикладних програм з підтримкою UTF-8 містила багато всякого роду прапорців командного рядка для активації відповідних режимів UTF-8, як от відома команда xterm -u8. Це виявилося дуже невдалою ідеєю. Для кожної команди в такому разі треба було пам'ятати її прапорець командного рядка, чому опції командного рядка не вважаються належним способом активації режиму UTF-8.

Правильним способом активації UTF-8 є механізм локалей POSIX. Локаль - це настрої конфігурації, які містять інформацію про спечифічні для певної культури умовності поведінки програмного забезпечення, включаючи кодування символів, позначення дати/часу, правила алфавітного сортування, систему вимірів і поширений формат офісного паперу тощо. Назви локалей зазвичай складаються з коду мови у форматі ISO 639-1, коду країни у форматі ISO 3166-1, а також іноді з назви кодування або інших класифікаторів.

За допомогою команди locale -a ви можете вивести список усіх інстальованих локалей (зазвичай у /usr/lib/locale/). Привласніть змінній оточення LANG назву локалі, якій ви надаєте перевагу. Під час виконання програмою C функції setlocale(LC_CTYPE, ""), бібліотека перевіряє змінні оточення LC_ALL, LC_CTYPE і LANG саме в цій послідовності, і перша з них, яку було задано, визначає, які дані локалі завантажити для категорії LC_CTYPE (яка керує функціями багатобайтових перетворень). Дані локалі поділяються на окремі категорії. Наприклад LC_CTYPE визначає кодування символів, а LC_COLLATE - порядок сортування ланцюжків. Змінна оточення LANG використовується для задання уставної локалі для всіх категорій, але змінні LC_* можна використати, щоб переважити окремі категорії. Не надто перейматеся ідентифікаторами країни в локалях. Такі локалі, як en_GB (англійська у Великобританії) та en_AU (англійська в Австралії) відрізняються звичайно тільки категорією LC_MONETARY (назвою грошових одиниць і правилами виводу грошових сум), яка практично не використовується додатками Лінукса. LC_CTYPE=en_GB та LC_CTYPE=en_AU практично тотожні.

Ви можете визначити назву кодування символів у поточній локалі командою locale charmap. Вона повиння вивести UTF-8, якщо вдалося задіяти локаль UTF-8 у категорії LC_CTYPE. Команда locale -m повертає список назв усіх встановлених кодувань символів.

Якщо ви користуєтесь виключно багатобайтові функції бібліотеки C для обернення зовнішнього кодування символів на кодування wchar_t, використовуване вами внутрішньо, тоді бібліотека C подбає про те, щоб використати правильне кодування, згідно LC_CTYPE, і ви ваша програма навіть може і не знати, яким є поточне багатобайтове кодування.

Проте, якщо ви надаєте перевагу тому, щоб не робити всього за допомогою багатобайтових функцій libc (наприклад тому, що гадаєте, що це вимагатиме забагато змін у програмному забезпеченні, або - не надто ефективно), тоді ваша програма повинна сама визначити, коли активувати режим UTF-8. Для цього, на системах, які відповідають стандартові X/Open, існує <langinfo.h>, і ви можете скористатися з рядка

utf8_mode = (strcmp(nl_langinfo(CODESET), "UTF-8") == 0);

для того, щоб визначити, чи поточна локаль користується кодуванням UTF-8. Вам слід, звичайно, спершу додати setlocale(LC_CTYPE, "") на початку вашої програми, щоб задати локаль згідно змінних оточення. Команда locale charmap так само викликає стандартну функцію nl_langinfo(CODESET), щоб визначити назву кодування поточної локалі. Ця функція наявна на практично будь-якому сучасному Юніксі. FreeBSD додала підтримку nl_langinfo(CODESET), починаючи з версії 4.6 (у червні 2002-го). Якщо вам треба, щоб autoconf перевірив на наявність nl_langinfo(CODESET), то ось що пропонує Бруно Гейбл:

======================== m4/codeset.m4 ================================
#serial AM1

dnl From Bruno Haible.
 
AC_DEFUN([AM_LANGINFO_CODESET],
[
  AC_CACHE_CHECK([for nl_langinfo and CODESET], am_cv_langinfo_codeset,
    [AC_TRY_LINK([#include <langinfo.h>],
      [char* cs = nl_langinfo(CODESET);],
      am_cv_langinfo_codeset=yes,
      am_cv_langinfo_codeset=no)
    ])
  if test $am_cv_langinfo_codeset = yes; then
    AC_DEFINE(HAVE_LANGINFO_CODESET, 1,
      [Define if you have <langinfo.h> and nl_langinfo(CODESET).])
  fi
])
=======================================================================

Ви також можете перевірити змінні оточення самостійно, без застосування setlocale(). У послідовності LC_ALL, LC_CTYPE, LANG, знайдіть першу з цих змінних середовища, яка має заданим якесь значення. Зробіть режим UTF-8 уставним (що можна проте переважити прапорцями командного рядка), якщо це значення містить підланцюжок UTF-8, позаяк це напевне вказує на запит до бібліотеки C використати локаль UTF-8. Ось приклад коду, який здійснює це:

char *s;
int utf8_mode = 0;
 
if (((s = getenv("LC_ALL"))   && *s) ||
    ((s = getenv("LC_CTYPE")) && *s) ||
    ((s = getenv("LANG"))     && *s)) {
    if (strstr(s, "UTF-8"))
        utf8_mode = 1;
}

Звичайно, цей код покладається на те, що всі локалі UTF-8 включають кодування в свою назву, що не завжди справджується, тому запит nl_langinfo() є ліпшою методою. Якщо вас турбує те, що nl_langinfo() може виявитись не досить портабельною, то можете скористатися з вільного емулятора nl_langinfo(CODESET) Маркуса Кухна для систем, які не включають його (або подібним від Бруно Гейбл), так само як функцією norm_charmap() для стандартизації виводу nl_langinfo(CODESET) на відмінних платформах.

Де знайти версію UTF-8 xterm?

Версія xterm, яка постачається з XFree86 4.0 і вище (утримувана Томасом Дікі) вже містить підтримку UTF-8. Для її активації, запустіть xterm у локалі UTF-8 і скористайтеся з шрифтів у кодуванні iso10646-1, як от

LC_CTYPE=en_GB.UTF-8 xterm \
    -fn '-Misc-Fixed-Medium-R-SemiCondensed--13-120-75-75-C-60-ISO10646-1'

після цього можете вивести (командою cat(1)) який файл-приклад на кшталт UTF-8-demo.txt у новому вікні xterm, і насолоджуйтесь побаченим.

Якщо ваша верія XFree86 старша за 4.0, тоді вам слід окремо завантажити найостаннішу розробницьку версію xterm і компілювати її власноруч, вказавши

./configure --enable-wide-chars
make # або xmkmf
make Makefiles
make
make install
make install.man

Якщо у вашій системі немає підтримки локалей UTF-8, то скористайтеся з прапорця командного рядка -u8 під час виклику xterm, щоб перевести ввід і вивід у UTF-8.

Наскільки добре xterm підтримує Юнікод?

Де знайти шрифти ISO 10646-1 для X?

Як щодо емуляторів терміналу в поєднанні з UTF-8?

Які додатки можна знайти, що працюють з UTF-8?

Які можна знайти латки для покращення підтримки UTF-8?

Чи існують спеціальні бібліотеки для підтримки Юнікоду?

Яким є статус підтримки юнікоду бібліотеками різноманітних елементів інтерфейсу?

Які пакети з підтримкою UTF-8 ще у стані розробки?

Як підтримка UTF-8 працює в Solaris?

Чи можна користуватись UTF-8 в Тенетах?

Чи існує якийсь зв'язок між назвами гліфів PostScript та кодами UCS?

Чи існують якісь чітко-означені підмножини UCS?

На що слід звернути увагу при перетворенні кодувань?

Чи готовий до Юнікоду X11?

Чи є якісь однорядкові сценарії Perl для роботи з UTF-8?

Як мені вводити символи Юнікоду?

Чи існують якісь хороші списки розсилки із цим питанням?

Дальші посилання

Addison-Wesley, 2006,
ISBN 0-321-48091-0.