При использовании команды ls, какие правила влияют на порядок имен, содержащих числа?

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

Когда переменная 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)

Выведет raw в одном Cолонке список ненаблюдаемых имен файлов, заканчивающихся на .gz numerically (числа сравниваются числово, а остальная часть основывается на порядке слияния).

Добавление 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, результат будет исходить из уникальных кодов символов, и упоминавшийся порядок станет:

  1. abc.zml-1.gz
  2. abc.zml-12.gz
  3. abc.zml-2.gz

Изменяя LC_COLLATE на en_US.UTF-8 или fr_FR.utf8, упорядочивание по сути игнорирует незначащие символы, такие как дефисы и точки, что приводит к:

  1. abc.zml-12.gz
  2. abc.zml-1.gz
  3. abc.zml-2.gz

Этот порядок объясним, так как цифра «12» рассматривается как единое значение и сравнивается с «1» и «2», что, согласно правилам, предшествует последующим значениям.

Применение

Если возникает необходимость в специфическом порядке упорядочивания, могут быть применены различные подходы:

  1. Изменение Локали: Установите переменную LC_COLLATE в значение, которое наиболее отвечает вашим требованиям для текущей задачи.

  2. Использование Внешних Скриптов или Утилит: Для более сложных случаев рекомендуется использовать утилиты, такие как GNU sort с флагом -V для «версионного» сортирования, что позволит учитывать числовые последовательности. Пример команды:

    ls | sort -V
  3. Подготовка Файловых Имен: Для обеспечения корректности сортировки можно использовать префиксы с нулями: 001, 012.

  4. Применение Скриптов на Оболочках, как ZSH: В оболочке ZSH можно применять квалификаторы, такие как (n) для числового сортирования файлов. Скрипт будет выглядеть так:

    print -rC1 -- *.gz(n)

Этот подход обеспечивает гибкость и контроль над процессом сортировки в разных сценариях.

Официальные Источники

Для изучения официальных правил, используемых при сортировке, рекомендуется ознакомиться с источниками конфигурации локали, такими как файлы локалей GNU libc. Они могут находиться в директории $prefix/share/i18n/locales/ и содержат карты символов и весов, применяемых при колляции.

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

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

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