В чём разница между массивами bash?

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

Если заголовок звучит запутанно… да.

У меня есть массив путей, который я собираю следующим образом:

ROOT=$(git rev-parse --show-toplevel)

shopt -s globstar
SOURCES=("$ROOT"/Libraries/**/i18n)
shopt -u globstar

echo "$SOURCES" выводит только первый элемент, а echo "${SOURCES[@]}" выводит всё, как и должно быть.

Я передаю этот массив в функцию таким образом, что, по странному стечению обстоятельств, на самом деле не должно работать, но на практике работает довольно хорошо: compile_mo_files SOURCES с помощью

compile_mo_files()
{
  if [[ $1 != SOURCE_DIRECTORIES ]]; then
    local -n SOURCE_DIRECTORIES=$1 # массив директорий, содержащих .po файлы
  fi
  [...]

Затем я прохожусь по нему в цикле “как это обычно делается”:

[...]
for SOURCE in "${SOURCE_DIRECTORIES[@]}"; do
  find "$SOURCE" -name "*.po" |
  {
    [...]

и $SOURCE вмещает каждую директорию очень красиво, приводя к обильным результатам find.

… Теперь я хочу исключить одну из найденных директорий, и наткнулся на этот номинально удобный способ:

readarray -d '' SOURCES < <(find "$ROOT" -name i18n -not -path '*/TheWoodenPath/*')

заменяя трёхстрочный код в первом блоке кода.

Но этот “массив”, как кажется, не является массивом:

  • echo "$SOURCES" теперь выводит весь массив вместо первого элемента
    (echo "${SOURCES[@]}" также выводит весь массив, если что)
  • Когда я передаю это в функцию, оно превращается в единую строку, по которой нельзя пройтись в цикле.
    (Цикл входит один раз и SOURCE содержит всю строку, а не один элемент.)
  • Когда я перемещаю цикл из функции непосредственно под readarray, он работает нормально, если что.

Руководство по mapfile (псевдоним readarray) называет это массивом. Очевидно, readarray тоже считает это массивом. Однако, согласно руководству по массивам, оно не ведет себя как массив.

  • Что это такое?
  • Могу ли я преобразовать это в нормальный/стандартный/хорошо работающий массив?
  • Стоит ли?
    (Более точно, поскольку различные части руководства утверждают разные свойства для обоих типов “массивов”, который из них является/претендует быть “каноническим истинным” массивом bash?)
  • Если нет,
    • как обойти этот массив в цикле?
    • как преобразовать массив с глобами в один из этих?
  • Я сломал свою систему Debian 12 и в данный момент на старой Debian 10 GNU bash, версия 5.0.3(1)-release (x86_64-pc-linux-gnu) – исчезает ли разница в более молодых (или предстоящих) версиях?

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

Различие между массивами в Bash

Когда мы говорим о «массиве» в контексте Bash, важно понимать некоторые тонкости, возникающие при работе с массивами путем использования различных методов их создания и манипуляции. Рассмотрим ваш конкретный случай более подробно.

Создание массива с использованием globstar

В вашем первоначальном примере вы создаете массив SOURCES, используя globstar для поиска файлов:

ROOT=$(git rev-parse --show-toplevel)

shopt -s globstar
SOURCES=("$ROOT"/Libraries/**/i18n)
shopt -u globstar

При выполнении echo "${SOURCES[@]}", каждый элемент массива будет напечатан отдельно, что соответствует стандартному поведению массивов в Bash. Это связано с тем, что при использовании метасимволов (**) для сбора путей, каждый отдельный путь рассматривается как отдельный элемент массива.

Создание массива с использованием readarray

Вы попытались заменить этот метод новым, использующим readarray:

readarray -d '' SOURCES < <(find "$ROOT" -name i18n -not -path '*/TheWoodenPath/*')

readarray (или наследственное mapfile) действительно создает массив, однако он ведет себя по-другому, так как вся дифференциация записей в массиве будет определяться нулевым символом (-d ''), а не пробелами. Когда вы затем вызываете echo "${SOURCES[@]}", вся строка может напечататься как одно значение, если вы не учли терминаторы строк (нулевые байты).

Вопросы и ответы

  1. Что это за массив?
    Это массив, сформированный командой readarray, где элементы могут быть не совсем такими, как ожидается из-за особенностей обработки нулевых байтов и ввода из команды find.

  2. Можно ли его конвертировать в стандартный массив?
    Да, для этого нужно убедиться, что разделители обрабатываются корректно, и перераспределить элементы в стандартный массив, если это нужно. Возможно, нужно будет удалить нулевые байты.

  3. Стоит ли конвертировать? Какой из них более стандартный?
    Использование readarray оправдано для обработки сложных символьных разделений, в то время как использование globstar удобно для манипуляций с путями. Выбор зависит от конкретных требований приложения.

  4. Как обойти ограничения?

    • Для правильного перебора с readarray следует убедиться, что разделение учитывает пробелы и специфическую терминологию.
    • Если необходимо, можно преобразовать массив в новый с помощью чтения в цикле, где обработка будет учтена.
  5. Влияет ли версия Bash на поведение?
    Версия 5.0.3 поддерживает все описанные функции, но оптимизация работы с массивами может изменяться в новых версиях. Для решения специфических проблем может быть полезно обновление, но в большинстве случаев отличие заключается в способе работы с вводом/выводом данных, а не в самой функции массивов.

Используйте подходящие методы, опираясь на практические тесты, чтобы убедиться в корректности работы. Это повысит надежность и предсказуемость кода на Bash.

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

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