Какое значение для LANG мне использовать, чтобы “sort -u” правильно обрабатывал китайские символы?

Вопрос или проблема

Контекст

Сегодня я хотел сохранить уникальные строки в файле, содержащем китайские символы. Я решил использовать утилиту sort, потому что мне известен этот инструмент, и удалить повторяющиеся строки в файле так же просто, как использовать флаг -u. Я узнал, что мне нужно изменить локаль, чтобы sort работал корректно с китайскими символами. Я заметил, что использование различных локалей заставляло sort вести себя по-разному. В этом посте я показал, что я нашел.

Я осознаю, что задача по удалению повторяющихся строк в файле может быть выполнена с использованием нескольких инструментов/языков программирования. Хотя я благодарен любому, кто предложит инструмент для выполнения этой задачи, меня больше интересует возможность узнать больше о локалях и о том, как они влияют на утилиты Unix.

Обнаруженные проблемы

Проблема № 1

Следующая информация о локали моей системы.

locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

Рассмотрим следующий файл с именем main.txt

䔍
䏝

Если я попытаюсь отсортировать его, используя en_US.UTF-8 в качестве моего $LANG, я потеряю строку № 2.

sort -u main.txt

Я решил эту проблему, установив LANG на zh_CN.UTF-8.

export LANG=zh_CN.UTF-8
sort -u main.txt
䔍
䏝

Я создал вопрос об этой проблеме на Stack Overflow. Кто-то предложил мне изменить локаль, и это, похоже, сработало; я думал, что я решил проблему, но затем я нашел Проблему № 2 и Проблему № 3 (описанные ниже).

Проблема № 2

Теперь предположим, что мы добавляем две строки в наш файл main.txt.

䔍
䏝
𠵇
𠳐

Если я выполню sort -u на нем, имея LANG, равный zh_CN.UTF-8, я потеряю строку № 4.

export LANG=zh_CN.UTF-8
sort -u main.txt
䔍
𠵇
䏝

Я пробовал разные локали, которые начинались с zh.CN, поскольку символы являются китайскими. Некомментированные локали – это те, которые я пытался использовать.

grep '^#\?zh' /etc/locale.gen
zh_CN.GB18030 GB18030  
zh_CN.GBK GBK  
zh_CN.UTF-8 UTF-8  
zh_CN GB2312  
#zh_HK.UTF-8 UTF-8  
#zh_HK BIG5-HKSCS  
#zh_SG.UTF-8 UTF-8  
#zh_SG.GBK GBK  
#zh_SG GB2312  
#zh_TW.EUC-TW EUC-TW  
#zh_TW.UTF-8 UTF-8  
#zh_TW BIG5  

Я заметил, что sort -u работает так, как ожидается, при установке LANG на zh_CN.GBK или zh_CN.GB18030. Я решил эту проблему, используя либо zh_CN.GBK, либо zh_CN.GB18030.

Вот доказательство того, что это работает так, как ожидается, при установке LANG на zh_CN.GBK.

export LANG=zh_CN.GBK
sort -u main.txt
䔍
𠵇
𠳐
䏝

Вот доказательство того, что это работает так, как ожидается, при установке LANG на zh_CN.GB18030.

export LANG=zh_CN.GB18030
sort -u main.txt
䔍
𠵇
𠳐
䏝

Проблема № 3

Предположим, что мы добавим еще две строки в наш файл main.txt.

䔍
䏝
𠵇
𠳐
书
乙

Если я использую zh_CN.GB18030 или zh_CN.GBK, я потеряю строку № 6.

export LANG=zh_CN.GB18030
sort -u main.txt
书
䔍
𠵇
𠳐
䏝
export LANG=zh_CN.GBK
sort -u main.txt
书
䔍
𠵇
𠳐
䏝

Вопрос

Какое значение для $LANG я должен использовать, чтобы сделать так, чтобы sort действительно сохранял уникальные строки в следующем файле?

䔍
䏝
𠵇
𠳐
书
乙

Я ранее показал, что использование zh_CN.GB18030, zh_CN.GBK, zh_CN.UTF-8 или zh_CN не похоже на то, что работает.

sort -u выводит по одному из каждой набора строк, которые коллируют одинаково (имеют идентичный порядок сортировки) в локали.

На системах GNU (системах, использующих GNU libc) во многих локалях многие символы¹ имеют неопределенный порядок сортировки, что означает, что они в конечном итоге сортируются одинаково, также есть символы, которые явно определены как имеющие одинаковый порядок сортировки.

Когда байты не могут быть декодированы в текст, они часто игнорируются или считаются имеющими одинаковый порядок сортировки².

Таким образом, для того чтобы sort -u дал вам уникальные строки на основе сравнения байт с байтом независимо от кодировки текста, в которой он написан, вам нужна локаль, где каждый байт может быть декодирован в символ и локаль с полным порядком.

Самый простой способ – использовать локаль C, где есть соответствие один байт == один символ (некоторые байты могут вести к неопределенным символам), а сортировка основана на значении байта (по крайней мере, на системах с ASCII) и это единственная локаль, которую вы гарантированно найдете на любой данной системе.

В вашем случае, чтобы понять, почему zh_CN дает вам несколько лучшее поведение, вы можете взглянуть на определения локалей обычно в /usr/share/i18n/locale на системах GNU, где для zh_CN вы найдете:

LC_COLLATE
copy "iso14651_t1_pinyin"
END LC_COLLATE

Где iso14651_t1_pinyin имеет:

copy "iso14651_t1_common"

И затем указывает порядок для многих дополнительных китайских символов, в то время как en_US использует только iso14651_t1_common.

Если вы установите LC_CTYPE (через LANG) на zh_CN.GB18030 или zh_CN.GBK и попытаетесь обработать файл, закодированный в UTF-8, sort обычно не сможет декодировать строки в текст, поскольку байты текста, закодированного в UTF-8, обычно не формируют допустимый текст, закодированный в GB18030, и вы значительно усугубите проблему.

Здесь, если вы хотите получить уникальные (в соответствии с сравнением байт с байтом) строки, но также хотите получить “правильный” отсортированный вывод в вашей локали, вы можете сделать:

LC_ALL=C sort -u your-file | sort

Где первый sort удаляет дубликаты, а второй сортирует оставшиеся строки в соответствии с порядком колляции вашей локали.

Мы используем LC_ALL вместо LANG или LC_CTYPE+LC_COLLATE, потому что это переменная, которая переопределяет все (LC_ALL имеет приоритет над LC_individual_setting, который имеет приоритет над LANG), так что это все еще работает, если LANG или LC_COLLATE также установлены в окружении.

Теоретически, вы также могли бы сделать:

LC_ALL= LC_CTYPE=C LC_COLLATE=C sort

чтобы установить только настройки “типа символа” и “колляции”, оставляя другие категории локали без изменений (которые охватываются LANG или LC_xxx), но оставшаяся, используемая sort, это LC_MESSAGES, используемая, например, в сообщениях об ошибках, и если вы установите LC_CTYPE=C, только сообщения на английском США могут быть отображены корректно, поскольку в них содержатся только символы ASCII. Например:

$ LC_MESSAGES=zh_CN LC_CTYPE=C sort --version
sort (GNU coreutils) 9.1
Copyright (C) 2022 Free Software Foundation, Inc.
??? GPLv3+:GNU ???????? 3 ?????? <https://gnu.org/licenses/gpl.html>?
????????:??????????????
?????????,????????

? Mike Haertel ? Paul Eggert ???

Вы найдете больше деталей на:


¹ Строго говоря, основной единицей является не символ, а коллирующий элемент, который может состоять из более чем одного символа, как ch, который сортируется между h и i в некоторых чешских локалях.

² Имейте в виду, что некоторые реализации sort (не GNU sort) также могут зависнуть на символах NUL или чрезмерно длинных строках.

Ответ или решение

Чтобы корректно обрабатывать китайские символы с помощью команды sort -u, необходимо установить переменную окружения LANG в значение, которое поддерживает полное кодирование и определенный порядок сортировки для китайских иероглифов. Как вы уже заметили, использование LANG=zh_CN.UTF-8 может не всегда давать ожидаемые результаты из-за неопределенного порядка сортировки в некоторых локалях, в то время как zh_CN.GBK и zh_CN.GB18030 подчас ведут себя лучше.

Первое Решение: Использование LANG=zh_CN.GBK или LANG=zh_CN.GB18030

Оба эти варианта обеспечивают корректное отображение и сортировку китайских символов. Тем не менее, следует помнить, что при использовании GBK или GB18030 важно убедиться, что текстовые файлы закодированы в соответствующей кодировке. Ваша начальная кодировка — это UTF-8, и попытка декодировать UTF-8 символы в GBK или GB18030 может привести к ошибкам.

Второе Решение: Использование LC_ALL=C

Для достижения стабильных результатов с уникальными строками, рекомендуется использовать LC_ALL=C. Эта локаль обеспечивает порядок сортировки на основе значений байтов, что подходит для задач, где требуется сохранить уникальные строки независимо от их коллаторационной обработки:

LC_ALL=C sort -u main.txt | sort

Здесь первый вызов команды sort -u удаляет дубли, а второй sort сортирует результаты в соответствии с локализацией вашей системы, которая может быть настроена на поддержку китайских символов.

Обзор

В контексте выполнения сравнения, использование LC_ALL=C предоставит вам более предсказуемые результаты при обработке уникальности строк. В случае необходимости работы с конкретными локалями, такими как zh_CN.GBK или zh_CN.GB18030, убедитесь, что ваш файл кодируется в нужной системе, чтобы избежать неправильного отображения или ошибок сортировки.

Для работы с текстами на китайском языке, установление правильной локали имеет важное значение, и использование комбинации LC_ALL может быть наиболее универсальным решением, гарантируя соответствие как в задачах сортировки, так и в удалении дубликатов. Однако для специфических задач и при наличии информации о кодировке всегда стоит учитывать возможности конкретной локали.

Оцените материал
Добавить комментарий

Капча загружается...