Вопрос или проблема
Я пытаюсь написать оболочку, строго соответствующую стандарту POSIX, но стандарт не уточняет, как перейти от байтов к символам. Он предлагает использовать LC_CTYPE
, который в свою очередь ссылается на концепцию файла charmap, но нигде не указано, где эти файлы charmap находятся и как их читать. man 5 charmap
говорит, что они обычно находятся в /usr/share/i18n/charmaps
, но этого для меня недостаточно. Кроме того, на моей системе Linux файлы в этом каталоге закодированы в gzip, что, как я думаю, может быть несовместимо, но я нигде не нашел ничего об этом.
Я думаю, что должны быть какие-то стандартные утилиты C для получения подробностей текущей кодировки символов (иначе как кто-то может её использовать?), но мне не удалось найти ничего подобного в моем веб-поиске. Существуют функции setlocale
и nl_langinfo
, последняя из которых может дать вам имя текущей используемой кодировки символов, но это не помогает мне с её декодированием.
Или я должен знать и реализовать все это заранее?
После того как вы выполнили setlocale()
, MB_CUR_MAX
скажет вам, есть ли у вас многобайтовая кодировка символов или нет.
Если это многобайтовая кодировка, вы можете использовать функции POSIX mb*()
, такие как mbstowcs()
для преобразования многобайтовых строк символов в широкие символы.
Затем используйте либо isblank()
/isalpha()
.. в противоположность iswblank()
/iswalpha()
, если вам нужно узнать, является ли символ пробелом/буквой… Смотрите iconv()
для преобразования между набором символов. Все функции wc*()
для работы с широкими символами (имейте в виду, что не все системы используют символы Unicode для значений wchar_t).
Символы, используемые в синтаксисе самой оболочки, должны быть частью переносимого набора символов (в наши дни ASCII), поэтому они будут одинаковыми для всех локалей на практике¹.
Большая часть того, что делает оболочка, работает на уровне байтов, поэтому вы сможете обойтись без декодирования во многих случаях. Также стоит рассмотреть вопрос, что делать со строками, которые не могут быть декодированы в символы. Аргументы командной строки не обязательно должны быть текстом или могут быть текстом в различных кодировках, поэтому оболочки должны уметь справляться с этим. Они также должны быть способны обрабатывать изменение локали в процессе, например, после export LC_CTYPE=something-else
.
Для оболочки, написанной сегодня, я думаю, я бы поддерживал только UTF-8 как многобайтовую кодировку символов, как это делает mksh
(с -o utf8-mode
), так как многие другие многобайтовые кодировки символов опасны, особенно в оболочке, поэтому лучше избегать их. Тогда вы сможете обойтись без всего этого API mb*
и сделать декодирование/кодирование UTF-8 вручную.
Вы, вероятно, захотите проверить, как это делают другие оболочки, здесь не место для предоставления полного руководства. Вы обнаружите, что это часто выглядит не очень хорошо. Например, сопоставление шаблонов в bash с учетом обработки многобайтовых символов довольно ужасно, и это не то, что я рекомендовал бы эмитировать. Декодирование байтов, которые не могут быть декодированы в символы, в специальные значения wchar_t, как это делают zsh/python, вероятно, лучший подход для работы с не текстом, когда вам нужно выполнять текстовые операции со строками (такими как сопоставление шаблонов, обрезка, длина строки…).
Оболочки, которые имеют некоторый уровень поддержки многобайтовых символов, на которые вы, возможно, захотите обратить внимание, включают bash, zsh, ksh93, mksh, bosh и yash. yash не справляется с не текстом.
¹ Хотя POSIX, например, предлагает, что любой символ, считаемый пробелом в локали, может использоваться как разделитель в синтаксисе или имена переменных могут содержать любые алфавитно-цифровые символы, я настоятельно рекомендую не делать этого. Немногие оболочки это делают или делают это только частично, и это только усложняет жизнь вам и дает пользователям API, который не может быть надежным. Обратите внимание, что некоторые системы все еще имеют локали с ms-kangi в качестве кодировки. Эта кодировка не содержит символа обратной косой черты, и байт 0x5c там есть символ йена (¥). В общем, вам удается обойтись без этого, потому что инструменты, включая оболочки, не декодируют байты в символы, особенно в однобайтовых локалях, поэтому ¥ может обычно использоваться вместо \
, так как инструменты просто связывают значение обратной косой черты с байтом 0x5c.
Ответ или решение
Для декодирования байтов в символы в соответствии с POSIX-стандартами, необходимо учесть несколько важных аспектов, связанных с локализацией и работой с много байтовыми кодировками. В данной статье мы рассмотрим основные шаги для достижения этого, опираясь на спецификации POSIX и используемые функции в программировании на языке C.
Установление локали
Первым шагом является установка корректной локали с помощью функции setlocale()
. Эта функция позволяет задавать параметры локализации, которые определяют, как будет происходить работа с символами и строками:
#include <locale.h>
setlocale(LC_CTYPE, ""); // Установка локали по умолчанию
Установка локали считается обязательным шагом, поскольку без этого многие функции работы с символами не смогут корректно обработать данные в зависимости от текущей кодировки.
Определение много байтовой кодировки
После установки локали, мы можем проверить, является ли текущая кодировка много байтовой, используя постоянную MB_CUR_MAX
. Она возвращает максимальное количество байтов, которое может занимать один символ:
#include <stdlib.h>
size_t max_bytes = MB_CUR_MAX; // Получение максимального количества байтов для символа
Если значение больше 1, это указывает на использование много байтовой кодировки, такой как UTF-8.
Декодирование байтов в символы
Для конвертации много байтовых строк в широкие символы (wide characters) используется функция mbstowcs()
. Она позволяет преобразовать строку, закодированную в много байтовой кодировке, в строку широких символов:
#include <wchar.h>
const char *mb_str = "строка в много байтовой кодировке";
size_t len = mbstowcs(NULL, mb_str, 0); // Определение длины для выделения памяти
wchar_t *wc_str = malloc((len + 1) * sizeof(wchar_t));
mbstowcs(wc_str, mb_str, len + 1); // Конвертация
Работа с широкими символами
После декодирования можно воспользоваться wchar_t
для манипуляций с символами. Используйте функции, такие как iswalpha()
и iswblank()
, для проверки типов символов:
#include <wctype.h>
for (size_t i = 0; i < len; ++i) {
if (iswalpha(wc_str[i])) {
// Обработка алфавитного символа
}
}
Конвертация между кодировками
Если требуется конвертировать строки между различными кодировками, можно использовать функцию iconv()
. Эта функция предоставляет мощные возможности для работы с различными кодировками, что позволяет произвести необходимые преобразования.
Обработка ошибок
Важно также обрабатывать ошибки, возникающие при декодировании. В случае неправильного формата или недопустимых символов рекомендуется использовать специальные символы или коды, которые могут быть обработаны позже:
size_t result = mbstowcs(wc_str, mb_str, len + 1);
if (result == (size_t)-1) {
// Обработка ошибки, возможно, замените неверный символ
}
Заключение
Следуя вышеописанным шагам, вы сможете корректно декодировать байты в символы в POSIX-среде. Помните, что, хотя POSIX предоставляет множество возможностей, разнообразие кодировок требует внимательного подхода к обработке символов и строки, чтобы гарантировать совместимость и корректность работы вашего приложения.
Ваш подход к декодированию байтов в символы и работа с локализацией должны быть спроектированы с учётом особенностей используемых кодировок, чтобы избежать проблем с преобразованием данных в пользовательских интерфейсах и при обработке текстовой информации.