Как расширить содержимое массива по его имени? [дубликат]

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

У меня есть массив

declare -a arr0=("'1 2 3'" "'4 5 6'")

и переменная

x=0

Затем я создаю новую переменную с именем массива

tmp="arr$x"

и я хотел бы иметь возможность расширять содержимое arr0 из этой переменной tmp вот так

newArr=( "${!tmp}" )

и использовать newArr как обычный массив, например, использовать индексы и т. д.


Но когда я пытаюсь его напечатать сейчас, это выглядит так:

$ echo ${newArr[@]}
'1 2 3'

Хранится только первый элемент, и я не знаю, как это исправить.

Я также пытался создать newArr вот так

newArr=( "${!tmp[@]}" )

но тогда это еще хуже – только 0 печатается.

$ echo ${newArr[@]}
0

Итак, знаете ли вы, как использовать массив, если его имя хранится в другой переменной?

Это возможно с помощью eval:

$ declare -a array=( 1 2 3 4 )
$ echo "${array[@]}"
1 2 3 4
$ p=ay
$ tmp=arr$p
$ echo "$tmp"
array
$ echo "\${${tmp}[@]}"
${array[@]}
$ echo "newarray=(\"\${${tmp}[@]}\")"
newarray=("${array[@]}")
$ eval "newarray=(\"\${${tmp}[@]}\")"
$ echo "${newarray[@]}"
1 2 3 4
$

Команды, начинающиеся с echo, предназначены для иллюстрации, eval опасен.

Обратите внимание, что вышеуказанное не сохраняет индексы массивов для разреженных массивов.

Косвенное расширение имеет некоторые исключения, и использование ! в массивах является одним из исключений.

Из man bash:

Если первый символ параметра – восклицательный знак (!), вводится
уровень косвенности переменной. Bash использует значение переменной,
образованное из оставшейся части параметра как имя переменной; эта
переменная затем расширяется, и это значение используется в остальной
подстановке, а не значение самого параметра. Это известно как косвенное
расширение.

Исключением из этого являются расширения ${!prefix*} и ${!name[@]}
описанные ниже. ${!prefix*} Имена, совпадающие с префиксом. Разворачивается
в имена переменных, чьи имена начинаются с префикса, разделенные первым
символом специальной переменной IFS.

Как описано в BASH FAQ06 , одно из решений выглядит так:

arrA=("AA" "2" "4")
echo -e "массив arrA содержит: \c" && declare -p arrA
ref=arrA;
tmp=${ref}[@] #это можно настроить на [1], [2] и т.д. для ссылки на конкретные элементы массива
echo "Косвенная печать: ${!tmp}"

#Вывод
массив arrA содержит: declare -a arrA='([0]="AA" [1]="2" [2]="4")'
Косвенная печать: AA 2 4

bash 4.3 добавил поддержку ksh93-подобных namerefs.

Итак, в bash-4.3 или выше вы можете сделать:

a0[5]=something
x=0
typeset -n var="a$x"
printf '%s\n' "${var[5]}"

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

bash скопировал массивы ksh с их неуклюжей конструкцией. Создание копии массива в bash сложно, вы можете использовать вспомогательную функцию, такую как:

copy_array() { # Аргументы: <имя_исх_массива> <имя_целе_массива>
  eval '
    local i
    '"$2"'=()
    for i in "${!'"$1"'[@]}"; do
      '"$2"'[$i]=${'"$1"'[$i]}
    done'
}

Чтобы использовать, например:

$ a0[5]=123
$ x=0
$ copy_array "a$x" var
$ typeset -p var
declare -a var=([5]="123")

kshbash, который скопировал ksh) – единственные оболочки, где массивы разрежены (или ассоциативные массивы, ключи которых ограничены положительными числами) (также это единственные массивы, с индексами, начинающимися с 0 вместо 1, или где $array неразвернуто не расширяется до элементов, а до элемента с индексом 0). Это намного проще с другими оболочками.

  • rc: array_copy = $array
  • fish: set array_copy = $array
  • csh: set array_copy = ($array:q)
  • zsh: array_copy=("$array[@]")
  • yash: array_copy=("$array")
  • zsh или yash: array_copy=("${array[@]}"}

Для косвенной копии (где $var содержит имя исходного массива):

  • rc: eval array_copy = '$'$var
  • fish: eval set array_copy \$$var
  • csh: eval "set array_copy = (\$${var}:q)"
  • zsh: array_copy=("${(@P)var}")
  • yash (или zsh): eval 'array_copy=("${'"$var"'[@]}")'

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

Вопрос о развитии содержимого массива через его имя, представленный на форуме, уже имеет множество решений, однако, я постараюсь предложить более формализованный и подробный ответ на эту проблему. Решение в первую очередь связано с особенностями работы с массивами в оболочке bash и проблемами, возникающими при попытке косвенной индексации массивов.

Проблема

У вас есть массив arr0 и переменная x=0. Вы хотите получить доступ к содержимому массива, используя временное имя tmp="arr$x", и затем сохранить это содержимое в новом массиве newArr, чтобы работать с ним через индексы.

Решение через eval

eval может использоваться для осуществления косвенной индексации массива в bash. Однако, он считается небезопасным инструментом и его стоит применять с осторожностью, поскольку неправильное его использование может привести к неожиданному выполнению команд.

Вот пример кода для решения описанной задачи:

declare -a arr0=("'1 2 3'" "'4 5 6'")
x=0
tmp="arr$x"
eval "newArr=(\"\${$tmp[@]}\")"
echo "${newArr[@]}"

Объяснение

  1. Определение и инициализация:

    • declare -a arr0=("'1 2 3'" "'4 5 6'") создает массив arr0.
    • x=0 устанавливает значение переменной x.
  2. Создание переменной-ссылки:

    • tmp="arr$x" формирует имя массива, используя значение x.
  3. Использование eval:

    • eval используется для динамического построения команды, которая выполняет косвенное обращение к значению переменной tmp как к имени массива.
  4. Массив newArr:

    • Новый массив newArr создается с использованием значения, полученного из массива arr0.

Запасное решение через typeset (для Bash 4.3+)

С bash начиная с версии 4.3, вы можете использовать typeset -n, чтобы создать ссылку на массив:

declare -a arr0=("'1 2 3'" "'4 5 6'")
x=0
typeset -n newArr="arr$x"
echo "${newArr[@]}"

Заключение

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

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

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