Текст книги "Linux программирование в примерах"
Автор книги: Арнольд Роббинс
Жанры:
Программирование
,сообщить о нарушении
Текущая страница: 40 (всего у книги 55 страниц)
1. Используйте read()
и memcmp()
для написания простой версии программы cmp
, которая сравнивает два файла. Вашей версии не нужно поддерживать какие-нибудь опции.
2. Используйте макрос
и прямое сравнение каждого прочитанного символа для написания другой версии cmp
, которая сравнивает два файла. Сравните производительность этой версии с производительностью написанной в предыдущем упражнении.
3. (Средней трудности) Рассмотрите функции
и GLIBC getline()
. Полезна ли memcpy()
для их реализации? Набросайте с ее использованием возможную реализацию fgets()
.
4. (Трудное) Найдите исходный код GLIBC версии memcmp()
. Он должен быть на одном из CD-ROM с исходным кодом в вашем дистрибутиве GNU/Linux, или же вы можете найти его в сети. Исследуйте код и объясните его.
5. Проверьте свою память. Как tmpfile()
организует удаление файла, когда закрыт указатель файла?
6. Используя mkstemp()
и fdopen()
, а также другие необходимые функции или системные вызовы, напишите свою версию tmpfile()
. Протестируйте ее тоже.
7. Опишите преимущества и недостатки использования unlink()
для имени файла, созданного mkstemp()
, непосредственно после возвращения mkstemp()
.
8. Напишите свою версию mkstemp()
, используя mktemp()
и open()
. Как вы можете обеспечить те же гарантии уникальности, которые обеспечивает mkstemp()
?
9. Программы, использующие mkstemp()
, должны обеспечивать удаление файла при завершении. (Предположим, что файл не удаляется сразу же после открытия по тем или иным причинам.) Это включает в себя случай, когда может поступить сигнал завершения. Поэтому удаление файла должно быть частью перехватчика сигнала. Как вы это сделаете?
10. (Трудное) Даже с урезанной очисткой при обработке сигнала все еще имеется состояние гонки. Есть небольшое окно между созданием временного файла функцией mkstemp()
и возвращением и записью его имени в переменной (для использования функцией обработки сигнала). Если в это окно попадает не перехваченный сигнал, программа завершается и оставляет временный файл. Как вы закроете это окно? (Спасибо Jim Meyering.)
11. Попробуйте откомпилировать и запустить ch12-setjmp.c
на как можно большем количестве различных систем с использованием как можно большего количества различных компиляторов, к каким у вас есть доступ. Попробуйте компилировать с различными уровнями оптимизации. Какие изменения поведения вы видели (если они были)?
12. Посмотрите файл /usr/src/libc/gen/sleep.c
в дистрибутиве исходного кода V7 Unix. Он содержит реализацию функции sleep()
, описанную в разделе 10.8.1 «Сигнальные часы: sleep()
, alarm()
и SIGALARM
». Распечатайте ее и прокомментируйте в стиле наших примеров, чтобы объяснить ее работу.
13. Посмотрите справочную страницу lrand48(3) на системе GNU/Linux или System V. Выглядит ли этот интерфейс более простым или трудным для использования, чем random()
?
14. Возьмите ch08-nftw.c
из раздела 8.4.3 «Перемещение по иерархии: nftw()
» и добавьте опцию –exclude=pattern
. Файлы, соответствующие паттерну, не должны выводиться.
15. (Трудное) Почему GLIBC нужны указатели на альтернативные версии функций стандартных каталогов и stat()
? Не может ли она вызывать их непосредственно?
16. Измените ch12-glob.c
для использования функции wordexp()
. Поэкспериментируйте с ней, проделав несколько дополнительных вещей, которые она предоставляет. Не забудьте взять аргументы командной строки в кавычки, чтобы wordexp()
на самом деле выполнила свою работу!
17. Стандартная grep
выводит имя файла, лишь когда в командной строке указано больше одного файла. Сделайте так, чтобы ch12-grep.c
действовала таким же образом.
18. Посмотрите справочную страницу grep(1). Добавьте к ch12-grep.c
стандартные опции -e
, -s
и -v
.
19. Напишите простую замещающую программу:
subst [-g] шаблон подстановка [файлы ...]
Она должна читать текстовые строки из указанных файлов или из стандартного ввода, если они не указаны. Каждая строка должна сравниваться на соответствие шаблону. Если обнаружено соответствие, оно должно замещаться подстановкой.
Если указана опция -g
, замещаться должно не только первое совпадение, но и все остальные совпадения в строке.
Глава 13
Интернационализация и локализация
Ранние вычислительные системы обычно для своего вывода (приглашений, сообщений об ошибках) и ввода (ответы на запросы, такие, как «да» и «нет») использовали английский язык. Это было верно для систем Unix вплоть до середины 1980-х. В конце 80-х, начиная с первого стандарта ISO для С и продолжая стандартами POSIX 1990-х и современным стандартом POSIX, были разработаны возможности для работы программ на нескольких языках без необходимости поддержки нескольких версий одной и той же программы. Данная глава описывает, как современные программы должны справляться с многоязычными проблемами.
13.1. ВведениеЦентральной концепцией является окружение, место, в котором работает программа. Локали содержат в себе следующие сведения: локальный набор символов; сведения о формате отображения даты и времени; форматирование и отображение денежных сумм; форматирование и отображение числовых значений (с или без разделителей тысяч, какой символ используется в качестве разделителя дробной части числа и т.д.).
Интернационализация является процессом написания (или изменения) программы таким образом, что она может работать с различными локалями. Локализация является процессом приспособления интернационализированной программы для определенной локали. Часто вместо этих терминов используют сокращения i18n и l10n соответственно. (Числовое значение указывает, сколько букв в середине слова, а эти сокращения имеют небольшое сходство с полными терминами.[136]136
От английских слов i(nternationalizatio)n и l(ocalizatio)n —Примеч. перев.
[Закрыть] Их также гораздо легче набирать.) Другим часто встречающимся термином является поддержка родного языка, обозначаемая как NLS[137]137
NLS – native language support – Примеч. перев.
[Закрыть]; NLS обозначает программную поддержку для i18n и l10n.
Кроме того, некоторые люди используют термин глобализация (сокращенно g10n) для обозначения процесса подготовки всех возможных локализаций для интернационализированной программы. Другими словами, подготовки программы для глобального использования.
Возможности NLS существуют на двух уровнях. Первым уровнем является библиотека С. Она предоставляет сведения о локали; процедуры для обработки большей части низкоуровневых подробностей работы по форматированию даты/времени, числовых и денежных значений; и процедуры для корректного для данной локали сопоставления регулярных выражений и классификации символов и сравнений. Именно возможности библиотеки появляются в стандартах С и POSIX.
На уровне приложения GNU gettext
предоставляет команды и библиотеку для локализации программы: т.е. для возможности вывода сообщений на одном или более естественных языках. GNU gettext
основана на плане, первоначально разработанном Sun Microsystems для Solaris[138]138
Существует более ранний дизайн, известный как catgets()
. Хотя он стандартизован POSIX, его гораздо сложнее использовать, и мы его не рекомендуем – Примеч. автора.
[Закрыть]; однако, она была реализована с нуля и теперь предоставляет расширения к первоначальному gettext
Solaris. GNU gettext
является стандартом де-факто для локализации программ, особенно в мире GNU.
В дополнение к локалям и gettext
стандарт С предоставляет возможности для работы с несколькими наборами символов и с их кодировками – способом представления больших наборов символов с помощью меньшего числа байтов. Мы кратко затронем эти проблемы в конце главы.
Специфичное для локали поведение управляется посредством установки переменных окружения, описывающих, какую локаль (локали) использовать для той или иной информации. Число доступных локалей, предлагаемых каждой конкретной операционной системой, колеблется от менее чем десяти на некоторых коммерческих системах Unix до сотен локалей на системах GNU/Linux. ('locale -a
' выводит полный список доступных локалей.)
Гарантируется существование двух локалей, «С» и «POSIX». Они действуют в качестве локали по умолчанию, предоставляя окружение 7-разрядного ASCII, поведение которого такое же, как на традиционных системах Unix без поддержки локалей. В противном случае, локали обозначают язык, страну, а также могут включать сведения о наборе символов. Например, 'it_IT
' используется для итальянского языка в Италии с использованием системного набора символов по умолчанию, a 'it_IT.UTF-8
' использует кодировку UTF-8 для набора символов Unicode.
Дополнительные подробности об именах локалей можно найти в справочной странице GNU/Linux setlocale(3). Обычно дистрибутивы GNU/Linux устанавливают для системы локаль по умолчанию при ее установке, основываясь на языке, выбранном тем кто устанавливал ее, и пользователям больше не приходится об этом беспокоиться.
Заголовочный файл
определяет функции и структуры локали. Категории локали определяют разновидности информации, которые будут для программы зависимы от локали. Категории доступны в виде набора именованных констант. Они перечислены в табл. 13.1.
Таблица 13.1. Константы категорий локалей ISO С, определенные в
LC_ALL | Эта категория включает всю возможную информацию локали. Она состоит из оставшейся части элементов этой таблицы |
LC_COLLATE | Категория для сравнения строк (обсуждаемого ниже) и областей регулярных выражений |
LC_CTYPE | Категория для классификации символов (заглавные, строчные и т.д.) Это влияет на сопоставление регулярных выражений и функции isXXX() в
|
LC_MESSAGES | Категория для специфичных для локали сообщений. Эта категория вступает в игру с GNU gettext , которая обсуждает далее в главе |
LC_MONETARY | Категория для форматирования денежной информации, такой, как локальные и международные символы для местной валюты (например, $ против USD для доллара США), форматирования отрицательных величин и т.д. |
LC_NUMERIC | Категория для форматирования числовых значений |
LC_TIME | Категория для форматирования дат и времени |
Эти категории определены различными стандартами. Некоторые системы могут поддерживать дополнительные категории, такие, как LC_TELEPHONE
или LC_ADDRESS
. Однако, они не стандартизованы; любой программе, которой нужно их использовать, но которая все равно должна быть переносимой, следует использовать #ifdef
для окружения соответствующих разделов.
По умолчанию, программы С и библиотека С ведут себя так, как если бы они находились в локали «С» или «POSIX» для обеспечения обратной совместимости со старыми системами. Однако, вызвав setlocale()
(как описано ниже), программа может включить действие локали. После того, как программа это сделала, пользователь может, установив переменные окружения, включать и выключать возможности локали, которые будет иметь программа.
Переменные окружения имеют те же самые имена, что и перечисленные в табл. 13.1 категории локалей. Таким образом, команда —
export LC_NUMERIС=en_DK LC_TIME=C
– определяет, что числа должны выводиться в соответствии с локалью 'en_DK
' (английский язык в Дании), но что значения даты и времени должны выводиться в соответствии с обычной локалью 'С
'. (Этот пример просто иллюстрирует, что вы можете указывать для различных категорий различные локали; это не является чем-то обязательным, что вы должны делать.)
Переменная окружения LC_ALL
перекрывает все другие переменные LC_xxx
. Если LC_ALL
не установлена, библиотека ищет определенные переменные (LC_CTYPE
, LC_MONETARY
и т.д.). Наконец, если ни одна из них не установлена, библиотека ищет переменную LANG
. Вот небольшая демонстрация с использованием gawk
:
$ unset LC_ALL LANG /* Удалить переменные по умолчанию */
$ export LС_NUMERIC=en_DK LC_TIME=C
/* Европейские числа, дата и время по умолчанию */
$ gawk 'BEGIN { print 1.234 ; print strftime() }'
/* Вывести число, текущие дату и время */
1,234
Wed Jul 09 09:32:18 PDT 2003
$ export LC_NUMERIC=it_IT LC_TIME=it_IT
/* Итальянские числа, дата и время */
$ gawk 'BEGIN { print 1.234 ; print strftime() }'
/* Вывести число, текущие дату и время */
1,234
mer lug 09 09:32:40 PDT 2003
$ export LC_ALL=C /* Установить перекрывающую переменную */
$ gawk 'BEGIN { print 1.234 ; print strftime() }'
/* Вывести число, текущие дату и время */
1.234
Wed Jul 09 09:33:00 PDT 2003
Для awk
стандарт POSIX констатирует, что числовые константы в исходном коде всегда используют в качестве десятичного разделителя '.
' тогда как числовой вывод следует правилам локали).
Почти все GNU версии стандартных утилит Unix могут использовать локали. Таким образом, особенно на системах GNU/Linux, установка этих переменных позволяет вам контролировать поведение системы[139]139
Программисты, долгое время работавшие на С и Unix, могут предпочесть использовать локаль 'С
', даже если их родной язык английский, английские локали дают другой результат по сравнению с тем, что ожидают эти седые, понюхавшие пороху ветераны Unix – Примеч. автора.
[Закрыть].
setlocale()
Как уже упоминалось, если вы ничего не делаете, программы на С и библиотека С ведет себя так, как если бы использовалась локаль «С». Функция setlocale()
устанавливает соответствующую локаль:
#include
char *setlocale(int category, const char *locale);
Аргумент category
является одной из категорий, описанных в разделе 13.2.1 «Категории локалей и переменные окружения». Аргумент locale
является строкой, именующей используемую для этой категории локаль. Когда locale
является пустой строкой (""
), setlocale()
проверяет соответствующие переменные окружения.
Если locale
равно NULL
, сведения о локали не изменяются. Вместо этого функция возвращает строку, представляющую текущую локаль для данной категории.
Поскольку каждая категория может быть установлена индивидуально, автор приложения решает, насколько будет программа использовать локаль. Например, если main()
делает лишь это —
setlocale(LC_TIME, "");
/* Использование локали только для времени и все */
– тогда, независимо от установленных в окружении других переменных LC_xxx
, локали подчиняются лишь функции времени и даты. Все остальные действуют так, как если бы программа по-прежнему работала в локали «С». Сходным образом вызов:
setlocale(LC_TIME, "it_IT"); /* Время всегда итальянское */
заменяет переменную окружения LC_TIME
(также, как LC_ALL
), заставляя программу использовать для вычислений времени/даты данные для Италии. (Хотя Италия может быть прекрасным местом, программам лучше использовать ""
, чтобы они могли корректно работать везде; этот пример предназначен лишь для объяснения того, как работает setlocale()
.)
Можно индивидуально вызывать setlocale()
для каждой категории, но простейшим способом является установка всего одним махом:
/* Находясь в Риме, вместо «всего» делайте все как римляне. :-) */
setlocale(LC_ALL, "");
Возвращаемое setlocale()
значение является текущей установкой локали. Это либо строковое значение, переданное в предыдущем вызове, либо непрозрачное значение, представляющее используемую вначале локаль. Это самое значение может быть затем передано обратно setlocale()
. Для последующего использования возвращаемое значение должно быть скопировано в локальное хранилище, поскольку это указатель на внутренние данные.
char *initial_locale;
initial_locale = strdup(setlocale(LC_ALL, "")); /* сохранить копию */
...
(void)setlocale(LC_ALL, initial_locale); /* восстановить ее */
Здесь мы сохранили копию, использовав функцию POSIX strdup()
(см. раздел 3.2.2 «Копирование строк: strdup()
»).
strcoll()
и strxfrm
()Знакомая функция strcmp()
сравнивает две строки, возвращая отрицательное, нулевое или положительное значения, если первая строка меньше, равна или больше второй. Это сравнение основано на числовых значениях символов в машинном наборе символов. Из-за этого результаты strcmp()
никогда не изменяются.
Однако, при наличии локалей простого числового сравнения недостаточно. Каждая локаль определяет для содержащихся в ней символов последовательность сортировки, другими словами, относительный порядок символов внутри локали. Например, в простом 7-битном ASCII у двух символов 'А
' и 'а
' десятичные значения равны 65 и 97 соответственно. Соответственно, во фрагменте
int i = strcmp("А", "a");
i
имеет отрицательное значение. Однако, в локали "en_US.UTF-8
" 'A
' идет после 'a
', а не перед ним. Таким образом, использование strcmp()
для приложений, использующих локаль, является плохой мыслью, мы могли бы сказать, что она возвращает игнорирующий локаль ответ.
Функция strcoll()
(string collate – сортировка строк) существует для сравнения строк с использованием локали:
#include
int strcoll(const char *s1, const char *s2);
Она возвращает такие же отрицательные/нулевые/положительные значения, что и strcmp()
. Следующая программа, ch13-compare.c
, интерактивно демонстрирует разницу:
1 /* ch13-compare.с – демонстрация strcmp() против strcoll() */
2
3 #include
4 #include
5 #include
6
7 int main(void)
8 {
9 #define STRBUFSIZE 1024
10 char locale[STRBUFSIZE], curloc[STRBUFSIZE];
11 char left[STRBUFSIZE], right[STRBUFSIZE];
12 char buf[BUFSIZ];
13 int count;
14
15 setlocale(LC_ALL, ""); /* установить локаль */
16 strcpy(curloc, setlocale(LC_ALL, NULL)); /* сохранить ее */
17
18 printf("–> "); fflush(stdout);
19 while (fgets(buf, sizeof buf, stdin) != NULL) {
20 locale[0] = ' ';
21 count = sscanf(buf, "%s %s %s", left, right, locale);
22 if (count < 2)
23 break;
24
25 if (*locale) {
26 setlocale(LC_ALL, locale);
27 strcpy(curloc, locale);
28 }
29
30 printf("%s: strcmp("%s", "%s") is %dn", curloc, left,
31 right, strcmp(left, right));
32 printf("%s: strcoll("%s", "%s") is %dn", curloc, left,
33 right, strcoll(left, right));
34
35 printf("n–> "); fflush(stdout);
36 }
37
38 exit(0);
39 }
Программа читает входные строки, состоящие из двух сравниваемых слов и необязательной локали, использующейся для сравнения. Если локаль дана, она становится локалью для последующих элементов. Программа начинает с любой локалью, которая установлена в окружении.
Массив curloc
сохраняет текущую локаль для вывода результатов; left
и right
являются левым и правым сравниваемыми словами (строки 10–11). Основную часть программы составляет цикл (строки 19–36), который читает строки и выполняет работу. Строки 20–23 разделяют входную строку, locale
инициализируется пустой строкой, если третья строка не предусмотрена.
Строки 25–28 устанавливают новую локаль, если она приведена. Строки 30–33 выводят результаты сравнения, а строка 35 приглашает для дальнейшего ввода. Вот демонстрация:
$ ch13-compare /* Запуск программы */
–> ABC abc /* Ввести два слова */
С: strcmp("ABC", "abc") is -1 /* Программа началась в локали "С" */
С: strcoll("ABC", "abc") is -1 /* В локали "С" идентичные рез-ты */
–> ABC abc en_US /* Слова те же, локаль "en_US" */
en_US: strcmp("ABC", "abc") is -1 /* strcmp() без изменений */
en_US: strcoll("ABC", "abc") is 2 /* рез-ты strcoll() изменились' */
–> ABC abc en_US.UTF-8 /* Слова те же, локаль "en_US.UTF-8" */
en_US.UTF-8: strcmp("ABC", "abc") is -1
en_US. UTF-8: strcoll("ABC", "abc") is 6
/* Другое значение, все еще положительное */
–> junk JUNK /* Новые слова */
en_US.UTF-8: strcmp("junk", "JUNK") is 1 /* предыдущая локаль */
en_US.UTF-8: strcoll("junk", "JUNK") is -6
Эта программа ясно показывает различие между strcmp()
и strcoll()
. Поскольку strcmp()
работает в соответствии с числовыми значениями символов, она всегда возвращает тот же самый результат, strcoll()
понимает проблемы сортировки, и ее результат меняется в соответствии с локалью. Мы видим, что в обеих локалях en_US
заглавные буквы идут после строчных.
ЗАМЕЧАНИЕ. Специфическая для локали сортировка строк является проблемой также и для сопоставления регулярных выражений. Регулярные выражения допускают диапазоны символов внутри выражений со скобками, такие, как '
[a-z]
' или '["-/]
'. Точное значение такой конструкции (символы, численно располагающиеся между начальной и конечной точками включительно) определено лишь для локалей «С» и «POSIX»Для локалей, не являющихся ASCII, такие диапазоны как '
[a-z]
' могут соответствовать также и заглавным буквам, а не только строчным! Диапазон '["-/]
' действителен в ASCII, но не в "en_US.UTF-8
".Долговременным наиболее переносимым решением является использование классов символов POSIX, таких, как '
[[:lower:]]
' и '[[:punct:]]
'. Если вам кажется, что нужно использовать выражения с диапазонами на системах, использующих локали, и на более старых системах, не использующих их, без изменения своей программы, решение заключается в применении грубой силы и индивидуальном перечислении каждого символа внутри скобок. Это неприятно, но это работает.
Основанная на локалях сортировка потенциально дорогостоящая. Если вы ожидаете большого числа сравнений, где по крайней мере одна из строк не будет изменяться или где значения строк будут сравниваться друг с другом по несколько раз (как при сортировке списка), следует рассмотреть использование функции strxfrm()
для преобразования своих строк для использования с strcmp()
. Функция strxfrm()
объявлена следующим образом:
#include
size_t strxfrm(char *dest, const char *src, size_t n);
Идея в том, что strxfrm()
преобразует первые n символов src
, помещая их в dest
. Возвращаемое значение является числом символов, необходимых для сохранения преобразованных символов. Если она превышает n, содержимое dest
«неопределенно».
Стандарт POSIX явным образом разрешает устанавливать в n
ноль, а в dest NULL
. В этом случае strxfrm()
возвращает размер массива, необходимого для сохранения преобразованной версии src
(не включая завершающий символ '