Вопрос или проблема
Когда переменная LC_COLLATE изменена, изменяются правила относительно порядка имен, содержащих числа.
LC_COLLATE=С ls -1
abc.zml-1.gz
abc.zml-12.gz
abc.zml-2.gz
В этом случае очевидно, что последовательность определяется последовательностью в таблице ASCII.
Теперь изменим значение переменной LC_COLLATE.
LC_COLLATE=”en_US.UTF-8″
(или zh_CN.utf8 или fr_FR.utf8 как значение)
abc.zml-12.gz
abc.zml-1.gz
abc.zml-2.gz
Здесь три разных значения переменной LC_COLLATE, которые имеют общее, что они относятся к UTF8, дают один и тот же результат.
abc.zml-2.gz идет после abc.zml-1.gz, что понятно.
Почему abc.zml-12.gz идет перед abc.zml-1.gz?
Где я могу прочитать официальные правила относительно порядка, в котором перечисляются файлы?
ls
по умолчанию сортирует файлы на основе порядка колlation имени в локали.
На системах GNU или любой системе, использующей GNU libc, исходное определение локалей en_US
можно увидеть в $prefix/share/i18n/locales/en_US
.
Там, в разделе LC_COLLATE
, который определяет порядок слияния, вы увидите:
copy "iso14651_t1"
(сам по себе имеющий copy "iso14651_t1_common"
).
Основано на (старой версии) таблице 1, найденной в приложении международного стандарта ISO 14651.
Большинство других локалей используют это, это не ограничивается en_US
.
Там (см. здесь для одной из недавно выпущенных glibc 2.41, хотя файл не изменялся с 2018 года), вы найдете:
% Third-level weight assignments
[...]
<MIN>
[...]
<CAP>
[...]
% First-level weight assignments
[...]
<S0030> % DIGIT ZERO
<S0031> % DIGIT ONE
<S0032> % DIGIT TWO
[...]
<S0067> % LATIN SMALL LETTER G
[...]
order_start <SPECIAL>;forward;backward;forward;forward,position
[...]
<U002D> IGNORE;IGNORE;IGNORE;<U002D> % HYPHEN-MINUS
<U002E> IGNORE;IGNORE;IGNORE;<U002E> % FULL STOP
[...]
<U0030> <S0030>;<BASE>;<MIN>;<U0030> % DIGIT ZERO
[...]
<U0031> <S0031>;<BASE>;<MIN>;<U0031> % DIGIT ONE
[...]
<U0032> <S0032>;<BASE>;<MIN>;<U0032> % DIGIT TWO
[...]
<U0047> <S0067>;<BASE>;<CAP>;<U0047> % LATIN CAPITAL LETTER G
[...]
<U0067> <S0067>;<BASE>;<MIN>;<U0067> % LATIN SMALL LETTER G
В этом порядке.
Эти последние несколько строк определяют вес для каждого слияющего элемента:
<коллатинг-элемент> <вес1>;<вес2>;<вес3>;<вес4> % комментарий
Вы заметите, что дефис и точка, как и большинство знаков препинания, имеют IGNORE
в качестве первичного, вторичного, третичного весов, только четвертый последний резервный определяется, в то время как ASCII десятичные цифры и буквы имеют все они.
При сравнении abc.zml-1.gz
с abc.zml-12.gz
сравнение будет сначала сделано на основе первичных весов. Поскольку вес -
и .
равен IGNORE
, это будет как сравнение abczml1gz
и abczml12gz
, и первичный вес 2
идет перед весом g
.
Если бы мы сравнивали abc.zml-1.gz
и abc.zml-1Gz
, все первичные и вторичные веса были бы одинаковыми, поэтому определение будет сделано на основе третичного веса, сравнивая <MIN><MIN><MIN><MIN><MIN><MIN><MIN><MIN><MIN>
с <MIN><MIN><MIN><MIN><MIN><MIN><MIN><CAP><MIN>
(взяв третичные веса каждого символа, где .
и -
все еще IGNORE
, поэтому удалены), и <MIN>
будет идти перед <CAP>
, так что то, с маленькой g
идет первым.
При сравнении abc.zml-1.gz
с abc-zml-1.gz
нам пришлось бы подняться до четвертого веса.
Это предназначено для имитации порядка, используемого в локали пользователя, как это делается, например, в словаре, где знаки пунктуации, регистр, диакритики обычно игнорируются в первую очередь, но могут использоваться для уточнения порядка при прочих равных (в этом случае некоторые локали предпочитают нижний регистр перед малым капсом и перед верхним регистром, некоторые делают ударения перед острым ударением…)
В локали C
на системах GNU порядок основывается на значении символов. Если LC_CTYPE
использует многобайтовое кодирование, такое как (но не ограничиваясь) UTF-8, это будет основываться на кодовой точке Unicode (от U+0000 до U+10FFFF; не все системы делают то же самое). Если нет (включая с LC_ALL=C
, что подразумевает LC_CTYPE=C
), то на значении байта. Например, €
(U+20AC) будет сортироваться после é
(U+00E9) с LC_CTYPE=en_US.UTF-8 LC_COLLATE=C
, но перед с LC_CTYPE=en_US.is0885915 LC_COLLATE=C
, потому что €
там закодирован как 0xA4
, а é
как 0xE9.
$ printf '\u20ac\n\u00e9\n' | iconv -t iso885915 | LC_CTYPE=en_US.iso885915 LC_COLLATE=C sort | iconv -f iso885915
€
é
$ printf '\u20ac\n\u00e9\n' | LC_CTYPE=en_US.UTF-8 LC_COLLATE=C sort
é
€
UTF-8 обладает этим свойством, что его кодировка сортируется по значению байта так же, как его символы по кодовой точке, так что для текста с кодировкой UTF-8 LC_CTYPE=en_US.UTF-8 LC_COLLATE=C
и LC_CTYPE=C LC_COLLATE=C
(или LC_ALL=C
) должны дать один и тот же результат. Последнее также будет гораздо менее ресурсозатратным и лучше работать в условиях неправильно закодированного текста.
Обратите внимание, что реализация ls
в GNU имеет -v
/ --sort=version
, который выполняет сортировку версий, а реализация sort
в GNU имеет -V
/--version-sort
, который может помочь ordenar вещи числовым образом. См. также модификатор n
глобальной переменной zsh
.
Например, в zsh:
print -rC1 -- *.gz(n)
Выведет r
aw в одном C
олонке список ненаблюдаемых имен файлов, заканчивающихся на .gz
n
umerically (числа сравниваются числово, а остальная часть основывается на порядке слияния).
Добавление 0 ко всем числам до одинаковой ширины обеспечивает сортировку по лексическому и числовому механизмам, одинаковую независимо от локали.
В zsh
добавить 0 ко всем числам во всех именах файлов в текущем рабочем каталоге до длины 3 цифры (осторожно, это также усекает более длинные числа до 3 цифр) можно с помощью:
autoload zmv
zmv '*' '${f//<->/${(l[3][0])MATCH}}'
Почему abc.zml-12.gz идет перед abc.zml-1.gz?
потому что 2
“до” .
, лексически, в выбранной вами коллации.
ls
не пытается сортировать по числам.
.
Ответ или решение
Вопрос о том, каким образом команда ls
упорядочивает имена файлов, содержащие числа, связан непосредственно с настройками локали и переменной окружения LC_COLLATE
. Давайте рассмотрим это подробно и методично.
Теория
В Unix-системах команда ls
сортирует имена файлов на основе порядка колляции, определённого локалью. Локаль выступает своего рода набором правил, которые задают, как должны интерпретироваться данные, включая алфавитные и числовые упорядочивания. Важную роль в этом процессе играет переменная окружения LC_COLLATE
. Она определяет языковое окружение, используемое для сравнения строк.
Когда LC_COLLATE
установлена в значение C
, системный порядок сортировки основывается на прямом соответствии символов их ASCII-кодам. Таким образом, упорядочивание происходит буквально, включая в себя каждый символ строки, включая знаки препинания, такие как точки или дефисы.
Пример:
abc.zml-1.gz
abc.zml-12.gz
abc.zml-2.gz
В этом случае файлы сортируются исходя из ASCII-кодов, где цифра 1
предшествует цифре 2
.
С другой стороны, при установке LC_COLLATE
в значение вроде en_US.UTF-8
, правила сортировки принимают во внимание сложные языковые особенности, где, например, дефисы и точки игнорируются при первичном сравнении. Это значит, что строки теперь сравниваются более «человечным» способом:
abc.zml-12.gz
abc.zml-1.gz
abc.zml-2.gz
Этот порядок объясняется тем, что числа рассматриваются без разделителей, и 12
становится меньше 1
.
Пример
Рассмотрим приведенный сценарий более подробно. Пусть у нас имеются следующие файлы:
abc.zml-1.gz
abc.zml-12.gz
abc.zml-2.gz
Если применить ls
с LC_COLLATE
установкой в C
, результат будет исходить из уникальных кодов символов, и упоминавшийся порядок станет:
abc.zml-1.gz
abc.zml-12.gz
abc.zml-2.gz
Изменяя LC_COLLATE
на en_US.UTF-8
или fr_FR.utf8
, упорядочивание по сути игнорирует незначащие символы, такие как дефисы и точки, что приводит к:
abc.zml-12.gz
abc.zml-1.gz
abc.zml-2.gz
Этот порядок объясним, так как цифра «12» рассматривается как единое значение и сравнивается с «1» и «2», что, согласно правилам, предшествует последующим значениям.
Применение
Если возникает необходимость в специфическом порядке упорядочивания, могут быть применены различные подходы:
-
Изменение Локали: Установите переменную
LC_COLLATE
в значение, которое наиболее отвечает вашим требованиям для текущей задачи. -
Использование Внешних Скриптов или Утилит: Для более сложных случаев рекомендуется использовать утилиты, такие как GNU
sort
с флагом-V
для «версионного» сортирования, что позволит учитывать числовые последовательности. Пример команды:ls | sort -V
-
Подготовка Файловых Имен: Для обеспечения корректности сортировки можно использовать префиксы с нулями:
001
,012
. -
Применение Скриптов на Оболочках, как ZSH: В оболочке ZSH можно применять квалификаторы, такие как
(n)
для числового сортирования файлов. Скрипт будет выглядеть так:print -rC1 -- *.gz(n)
Этот подход обеспечивает гибкость и контроль над процессом сортировки в разных сценариях.
Официальные Источники
Для изучения официальных правил, используемых при сортировке, рекомендуется ознакомиться с источниками конфигурации локали, такими как файлы локалей GNU libc. Они могут находиться в директории $prefix/share/i18n/locales/
и содержат карты символов и весов, применяемых при колляции.
Изучение и правильное понимание колляции данных позволит вам управлять процессом сортировки файлов более эффективно, учитывая как числовые, так и алфавитные аспекты с учетом назначения и специфики вашего проекта.