Непрямой возврат всех элементов в массиве

10

Страница руководства Bash описывает использование ${!a}для возврата содержимого переменной, имя которой является содержимым a(уровень косвенности).

Я хотел бы знать, как вернуть все элементы в массиве, используя это, т.е.

a=(one two three)
echo ${a[*]}

возвращается

one two three

Я хотел бы для:

b=a
echo ${!b[*]}

вернуть то же самое. К сожалению, нет, но возвращается 0.

Обновить

Учитывая ответы, я теперь понимаю, что мой пример был слишком прост, так как, конечно, что-то вроде:

b=("${a[@]}")

Достигнет именно то, что я сказал, что мне нужно.

Итак, вот что я пытался сделать:

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST=LIST_$whichone
LIST=${!_LIST[*]}

Конечно, внимательное прочтение справочной страницы Bash показывает, что это не будет работать так, как ожидалось, потому что последняя строка просто возвращает индексы «массива» $_LIST(а не массива вообще).

В любом случае, следующее должно сделать работу (как указано):

LIST=($(eval echo \${$_LIST[*]}))

или ... (маршрут, по которому я пошел, в конце концов)

LIST_lys="lys1 lys2"
...
LIST=(${!_LIST})

Предполагая, конечно, что элементы не содержат пробелов.

Эрик Смит
источник
Добавьте [@]к указателю, _LIST="LIST_${whichone}[@]"а затем используйте LIST=("${!_LIST}")для копирования массива. Рекомендуется использовать имена переменных в нижнем регистре, чтобы избежать конфликтов с переменными среды (все заглавные буквы).
Исаак

Ответы:

7

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

Например. Для вашего оригинального примера:

a=(one two three)
echo ${a[*]} # one two three
b=a
echo ${!b[*]} # this would not work, because this notation 
              # gives the indices of the variable b which
              # is a string in this case and could be thought
              # as a array that conatins only one element, so
              # we get 0 which means the first element
c='a[*]'
echo ${!c} # this will do exactly what you want in the first
           # place

Для последнего реального сценария, я полагаю, что код ниже будет работать.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[*]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one

Лучше использовать нотацию, чтобы "${var[@]}"избежать путаницы с $IFSрасширением и параметром. Вот окончательный код.

LIST_lys=(lys1 lys2)
LIST_diaspar=(diaspar1 diaspar2)

whichone=$1   # 'lys' or 'diaspar'

_LIST="LIST_$whichone"[@]
LIST=( "${!_LIST}" ) # Of course for indexed array only 
                     # and not a sparse one
                     # It is essential to have ${!_LIST} quoted
weynhamz
источник
8

Вам нужно скопировать элементы явно. Для индексированного массива:

b=("${a[@]}")

Для ассоциативного массива (обратите внимание, что aэто имя переменной массива, а не переменная, значением которой является имя переменной массива):

typeset -A b
for k in "${!a[@]}"; do b[$k]=${a[$k]}; done

Если у вас есть имя переменной в массиве, вы можете использовать поэлементный метод с дополнительным шагом для получения ключей.

eval "keys=(\"\${!$name[@]}\")"
for k in "${keys[@]}"; do eval "b[\$k]=\${$name[\$k]}"; done

(Внимание, код в этом посте был набран прямо в браузере и не тестировался.)

Жиль "ТАК - перестань быть злым"
источник
Вы совершенно правы, но, к сожалению, это не решает мою проблему (мой пример был слишком упрощенным). Я предоставил обновление, чтобы прояснить свои намерения.
Эрик Смит
@Eric Я думаю, что в ksh / bash тебе нужно eval на этом этапе. Смотрите мое редактирование.
Жиль "ТАК - перестань быть злым"
+1, но в соответствии с вашим заявлением об отказе, это на самом деле не совсем работает (последний бит кода), а требует лишь незначительных корректировок. В частности, оно должно быть \${!$name[@]}в первой строке, чтобы первое раскрытие имело только «$ name», и ${!a[@]}оно сохраняется для eval, и то же самое в цикле for с \${$name}. Две другие обратные косые черты перед $ k также не являются строго необходимыми.
krb686
2

${!b[*]}расширяется до индексов, используемых в массиве b.

То , что вы хотели бы должно быть сделано в два этапа, так что evalпоможет: eval echo \${$b[*]}. (Обратите внимание, \что $первый шаг пройдет первый шаг, расширение переменной, и будет расширен только на втором шаге eval.)

Согласно Параметру Расширение !используется как для косвенного расширения ( {!a}), так и для Префикса соответствия имен ( ${!a*}) и Списка ключей массива ( ${!a[*]}). Поскольку список ключей массива имеет тот же синтаксис, что и предполагаемое косвенное расширение + расширение элемента массива, последнее не поддерживается как есть.

manatwork
источник
2
${!a}это расширение к значению переменной , чье имя $a. Это довольно кратко описано в руководстве, в параграфе, который начинается с «Если первым символом параметра является восклицательный знак ( !), вводится уровень переменной косвенности».
Жиль "ТАК ... перестать быть злым"
Да - @Gilles прав, но @manatwork, во втором чтении, я заметил, что ${!это довольно амбициозно, поскольку, если вы работаете с массивом, поведение будет другим.
Эрик Смит
@ Жалеет, что вы правы в этом предложении, но, к сожалению, оно не применимо как «Исключением из этого являются расширения $ {! Prefix *} и $ {! Name [@]}, описанные ниже». Но мой ответ, безусловно, является неоднозначным, поэтому я его отредактирую.
manatwork
0

Для косвенного доступа к массивам просто добавьте [@]косвенную переменную b=a[@].

Если эти переменные установлены:

a=(one two three)
printf '<%s> ' "${a[@]}"; echo

Тогда это будет работать:

b="a[@]"
printf '<%s> ' "${!b}"

Или просто:

echo "${!b}"

Такой массив можно скопировать так:

newArr=( "${!b}" )

И затем напечатано с:

declare -p newArr

В одном скрипте:

#!/bin/bash
a=(one two three)
echo "original array"
printf '<%s> ' "${a[@]}"; echo

echo "Indirect array with variable b=a[@]"
b="a[@]"
printf '<%s> ' "${!b}"; echo

echo "New array copied in newArr, printed with declare"
newArr=( "${!b}" )
declare -p newArr

Конечно, все вышеперечисленное будет копировать не разреженный массив. Тот, в котором все индексы имеют значение.

разреженные массивы

Разреженный массив - это массив, который может иметь неопределенные элементы.
Например, a[8]=1234определяет один элемент, а в bash от 0 до 7 не существует.

Для копирования такого разреженного массива используйте этот метод

  1. Распечатать старый массив:

    $ oldarr[8]=1234
    $ declare -p oldarr
    declare -a oldarr=([8]="1234")
  2. Замените имя массива и захватите строку:

    $ str=$(declare -p oldarr | sed 's/oldarr=/newarr=/')
  3. Проверьте строку, созданную таким образом, новый массив был определен:

    $ eval "$str"
    $ declare -p newarr
    declare -a newarr=([8]="1234")
Исаак
источник