Вопрос или проблема
Если заголовок звучит запутанно… да.
У меня есть массив путей, который я собираю следующим образом:
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[@]}"
, вся строка может напечататься как одно значение, если вы не учли терминаторы строк (нулевые байты).
Вопросы и ответы
-
Что это за массив?
Это массив, сформированный командойreadarray
, где элементы могут быть не совсем такими, как ожидается из-за особенностей обработки нулевых байтов и ввода из командыfind
. -
Можно ли его конвертировать в стандартный массив?
Да, для этого нужно убедиться, что разделители обрабатываются корректно, и перераспределить элементы в стандартный массив, если это нужно. Возможно, нужно будет удалить нулевые байты. -
Стоит ли конвертировать? Какой из них более стандартный?
Использованиеreadarray
оправдано для обработки сложных символьных разделений, в то время как использованиеglobstar
удобно для манипуляций с путями. Выбор зависит от конкретных требований приложения. -
Как обойти ограничения?
- Для правильного перебора с
readarray
следует убедиться, что разделение учитывает пробелы и специфическую терминологию. - Если необходимо, можно преобразовать массив в новый с помощью чтения в цикле, где обработка будет учтена.
- Для правильного перебора с
-
Влияет ли версия Bash на поведение?
Версия 5.0.3 поддерживает все описанные функции, но оптимизация работы с массивами может изменяться в новых версиях. Для решения специфических проблем может быть полезно обновление, но в большинстве случаев отличие заключается в способе работы с вводом/выводом данных, а не в самой функции массивов.
Используйте подходящие методы, опираясь на практические тесты, чтобы убедиться в корректности работы. Это повысит надежность и предсказуемость кода на Bash.