Вопрос или проблема
Проблема следующая:
$ FILES=(foo/**/*.suffix bar/**/*.suffix2)
$ grep baz "${FILES[@]}" # работает
# Я использую это, чтобы создать одну локальную переменную с local в одной функции
$ SUBFILES="${FILES[@]}"
$ grep baz "${SUBFILES[@]}" # не работает
Я использовал od
, чтобы проверить их, но они одинаковые, по крайней мере имеют одинаковое количество “\n” и одинаковую длину (я не проверял каждый символ. Но на первый взгляд они одинаковые).
$ echo ${SUBFILES[@]} | od -c
$ echo ${FILES[@]} | od -c
Почему присваивание переменной не создает один объект, который все еще работает для grep
?
Присваивания массивов в bash
записываются
array=( elements )
В вашем случае,
SUBFILES=( "${FILES[@]}" )
Это назначит все элементы массива FILES
новому массиву SUBFILES
.
С SUBFILES="${FILES[@]}"
вы присваиваете одну строку переменной SUBFILES
. Эта строка будет содержать элементы FILES
, разделенные пробелами.
Смотрите также Существуют ли соглашения об именах для переменных в shell-скриптах?
Вы правы, b="${a[@]}"
не создает массив в Bash. Без скобок это присваивание скалярной переменной, независимо от того, что находится справа. Так что, даже если вы использовали индекс @
, чтобы запросить все элементы массива по отдельности, присваивание объединяет их в одну строку. Это означает, что b="${a[@]}"
довольно похоже на b="${a[*]}"
, за исключением того, что всегда используется пробел в качестве разделителя, вместо первого символа $IFS
.
Например:
$ a=("foo bar" doo);
$ IFS=:;
$ b="${a[@]}"
$ c="${a[*]}"
$ typeset -p b c
declare -- b="foo bar doo"
declare -- c="foo bar:doo"
Чтобы получить массив, нужно добавить скобки, то есть b=( "${a[@]}" )
скопирует простой массив, подобный вашему. Я говорю “простой”, поскольку индексы массива могут быть не последовательными, и при разворачивании эти индексы теряются, при том что присвоение создает новые, последовательные, начиная с 0
.
$ d[5]=xxx d[8]=yyzz
$ e=( "${d[@]}" )
$ typeset -p d e
declare -a d='([5]="xxx" [8]="yyzz")'
declare -a e="([0]="xxx" [1]="yyzz")"
Если вы хотите копию, которая сохраняет индексы, вероятно, безопаснее всего просто использовать цикл. В общем, переменные также имеют атрибуты, такие как экспорт и атрибуты целых чисел, и их тоже нужно будет вручную повторно установить. Не существует простого и понятного способа копировать переменную в полном объеме.
Есть расширение "${var@A}"
в более новых версиях Bash, которое предоставляет команду declare
для воссоздания переменной, но оно содержит оригинальное имя, поэтому не может сразу использоваться для копирования. Я полагаю, вы могли бы сделать что-то вроде этого, чтобы скопировать d
в f
…
$ d[5]=xxx d[8]=yyzz
$ d_repr="${d[@]@A}"
$ eval "declare -a f=${d_repr#declare -a d=}"
$ typeset -p d f
declare -a d=([5]="xxx" [8]="yyzz")
declare -a f=([5]="xxx" [8]="yyzz")
…но я бы был осторожен в реальной жизни и в любом случае это требует учета атрибутов оригинальной переменной. Например, целочисленный массив g
будет выводиться как declare -ai g=...
.
Обратите внимание, что echo
объединяет полученные аргументы пробелами, так что echo ${a[@]}
или echo "${a[@]}"
скрывает, где именно проходит граница между элементами массива. И когда присваивание скалярной переменной объединяет с пробелами, а echo
объединяет с пробелами, результат оказывается одинаковым в обоих этих случаях.
Это делает echo
очень плохим для анализа подобных вещей. Лучше использовать, например, printf
здесь, это более четко показывает, что b
— это просто один элемент.
$ unset a b
$ a=("foo bar" doo)
$ b="${a[@]}"
$ printf "<%s>\n" "${a[@]}"
<foo bar>
<doo>
$ printf "<%s>\n" "${b[@]}"
<foo bar doo>
Или использовать typeset -p
/ declare -p
.
Даже в zsh, где $a
разворачивает как массив (с отдельными элементами, разделенными пробелами), b=$a
все равно объединяет в строку (и так же b=$a[@]
), так что нужно знать, с каким типом переменной вы имеете дело.
Ответ или решение
Вопрос касается особенностей работы с массивами в Bash и различий в обработке данных при назначении переменных. Давайте разберем эту проблему подробно.
Описание проблемы
У вас есть массив FILES
, содержащий списки файлов, которые могут находиться в разных подпапках и иметь разные расширения. Использование grep
работает корректно при обращении к FILES
как к массиву: grep baz "${FILES[@]}"
.
Но в вашем втором примере вы пытаетесь скопировать массив в переменную SUBFILES
с помощью команды SUBFILES="${FILES[@]}"
. После этого вызов grep baz "${SUBFILES[@]}"
не работает как ожидается.
Причины проблемы
Ключевая ошибка заключается в том, что SUBFILES="${FILES[@]}"
преобразует массив во всего лишь одну строку, где все элементы массива разделены пробелами. Bash здесь приводит все элементы массива к одной строке, что фактически уничтожает структуру массива.
Когда вы используете "${FILES[@]}"
, каждый элемент массива передается как отдельный аргумент. Но при назначении SUBFILES
без скобок массив превращается в строку, где все элементы объединяются через пробел. Эта строка воспринимается как один элемент при дальнейшем использовании, и, следовательно, команда grep
не работает, ожидая несколько отдельных параметров.
Решение
Чтобы правильно скопировать массив, необходимо назначить его с помощью скобок:
SUBFILES=( "${FILES[@]}" )
Эта конструкция сохранит каждый элемент FILES
как отдельный элемент нового массива SUBFILES
.
Подробный пример
Рассмотрим на примере, как Bash ведет себя с массивами и строками:
a=("foo bar" "doo")
SUBFILES="${a[@]}" # Преобразование в строку
echo "$SUBFILES" # Выводит: foo bar doo - это одна строка
SUBFILES=( "${a[@]}" ) # Копирование массива
echo "${SUBFILES[@]}" # Выводит: foo bar doo - каждый элемент массива
Для диагностики используйте команду typeset -p
, которая показывает структуру переменной, позволяя понять, как она интерпретируется Bash:
typeset -p SUBFILES
Заключение
Понимание различий между строками и массивами в Bash жизненно важно для корректной работы скриптов. Даже небольшая ошибка в назначении переменных может привести к совершенно разным результатам. Используйте правильный синтаксис и диагностические инструменты, чтобы избежать подобных проблем.
Эта статья была подготовлена для увеличения вашего понимания работы с Bash, оптимизирована для SEO, и содержит все необходимые детали для профессионального применения.