Вопрос или проблема
Я устанавливаю свой ассоциативный массив следующим образом:
$ foo=test
$ set -A $foo "a b" "1 2" "c d" "3 4"
$ for key val in "${(@kv)test}"; do echo "$key -> $val" done
a b -> 1 2
c d -> 3 4
Как заменить содержимое массива "a b" "1 2" "c d" "3 4"
переменной?
set -A array value1 value2
— это древний синтаксис ksh с начала 80-х¹ для определения обычных массивов. Он не определяет ассоциативные массивы и поддерживается только для совместимости с ksh88.
Флаги расширения параметров k
и v
имеют смысл только для переменных, являющихся ассоциативными массивами. Для других типов переменных, включая обычные массивы, они просто игнорируются. Таким образом, здесь "${(@kv)test}"
то же, что и "${(@)test}"
или "$test[@]"
и разворачивается во все элементы обычного массива².
Чтобы объявить ассоциативный массив, вы должны использовать современный
assoc=(
'key 1' 'value 1'
'key 2' 'value 2'
)
синтаксис³ после объявления assoc
как ассоциативного массива с помощью:
typeset -A assoc
С недавними версиями, теперь, когда typeset
стал двойной командой / встроенной командой, вы также можете выполнить и объявление, и присвоение одновременно с помощью:
typeset -A assoc=(
'key 1' 'value 1'
'key 2' 'value 2'
)
Затем вы можете выполнить:
printf '"%s" => "%s"\n' "${(@kv)assoc}"
Или
for k v ("${(@kv)assoc}") print -r -- "$k => $v"
Или:
for k ("${(@k)assoc}") print -r -- "$k => $assoc[$k]"
Чтобы перебрать его ключи и значения.
Чтобы определить ассоциативный массив, имя которого хранится в переменной из списка ключей и значений, вы можете использовать:
setassoc() {
typeset -gA $1; shift
eval "$1"='( "$@[2,-1]" )'
}
foo=test
setassoc $foo 'key 1' 'value 1' 'key 2' 'value 2'
Или, если список ключей и значений хранится в обычной переменной массива:
array=('key 1' 'value 1' 'key 2' 'value 2')
setassoc $foo "$array[@]"
Кстати, set -A
мог бы использоваться здесь, чтобы избежать eval
(не то чтобы было что-то плохое в использовании eval
таким образом здесь), но нужно убедиться, что переменная установлена как ассоциативный массив вначале:
foo=test
typeset -A $foo
set -A $foo "$array[@]"
И будьте осторожны, если включена эмуляция ksh (особенно, если включена опция ksharrays
), ее нужно изменить на:
set -A $foo -- "$array[@]"
¹ Дэвид Корн выбрал -A
, так как set -a
из оболочки Bourne уже было занято. Это также объясняет, почему read -A
используется в ksh93 или zsh для чтения строки в массив. bash
не поддерживает set -A
, хотя он основывает свой дизайн массивов на ksh88 (в то время как zsh
ближе к дизайну массивов csh, чем ksh), и выбрал read -a
вместо read -A
. Возможный источник путаницы: typeset -a
объявляет обычный массив, в то время как typeset -A
используется для ассоциативных массивов (оба из ksh)
² и for k v
присваивает k
и v
элементи массива поочередно, что объясняет вывод и почему он в том же порядке, как в присвоении (в то время как для ассоциативных массивов порядок не гарантируется).
³ этот синтаксис на самом деле из zsh в 1990 году, и позже появился в ksh93 (хотя там, как часть большого вариационного типирования, которое также включает составные переменные) и bash (который не имел массивов до версии 2.0 в 1996 году), хотя он в значительной степени вдохновлен csh (первая оболочка с массивами из конца 70-х), где синтаксис set array = (foo bar)
@Stéphane Chazelas: Поскольку у нас есть такой изящный синтаксис для объявления и назначения ассоциативного массива одновременно, я попробовал избежать “древней” команды set -A
в вашем предложенном решении:
foo=test
typeset -A $foo
set -A $foo "$array[@]"
Однако, хотя typeset -A test=( "$array[@]" )
работает безупречно, возникает ошибка при использовании typeset -A $foo=( "$array[@]" )
. Говорится: bad pattern: test=( key 1
.
Ок, можно попробовать typeset -A $foo=( "${(qq)array[@]}" )
, чтобы элементы массива были заключены в кавычки, но сейчас ошибка такая: bad pattern: test=( 'key 1'
. Не намного лучше. Есть ли корректный способ сделать это?
.
Ответ или решение
Чтобы динамически задать имя и содержимое ассоциативного массива в ZSH, нужно правильно подходить к синтаксису и методам, которые используются для работы с переменными в этой оболочке. Вопрос поднимает тему создания и инициализации ассоциативного массива, используя переменную для имени массива и массив строк в качестве его содержимого.
Шаги для создания ассоциативного массива в ZSH
-
Объявление ассоциативного массива:
В ZSH ассоциативные массивы должны быть явно объявлены. Это можно сделать с помощью командыtypeset -A
. Например:typeset -A assocArray
-
Инициализация ассоциативного массива с динамическим именем:
Чтобы задать динамическое имя для вашего массива и наполнить его содержимым, вы можете использовать функциюsetassoc
. Эта функция объявляет массив и заполняет его данными:setassoc() { typeset -gA $1 eval "$1"='( "$@[2,-1]" )' }
$1
– это имя переменной, которой будет присвоен массив.eval
используется для динамического создания массива с указанными парами ключ-значение.
-
Передача данных в функцию:
Допустим, у вас есть массив данных:array=('key 1' 'value 1' 'key 2' 'value 2')
Вы можете вызвать функцию для создания ассоциативного массива с именем, хранящимся в переменной
foo
:foo=test setassoc $foo "$array[@]"
-
Обход и печать ассоциативного массива:
Чтобы вывести ключи и значения массива, можно использовать следующий синтаксис:for key value in "${(@kv)test}"; do echo "$key => $value" done
Практические замечания
- Учитывайте версии ZSH: Новые возможности команд
typeset
и их использование могут зависеть от версии ZSH, с которой вы работаете. - Оптимизация и кросс-платформенность: Следует избегать устаревших команд вроде
set -A
, особенно если вы планируете переносить скрипты на другие системы или оболочки. - Использование eval: Хотя
eval
позволяет гибко и динамично задавать массивы, стоит использовать его осторожно и лишь в надежных контекстах, чтобы избежать ненамеренного выполнения вредоносного кода.
Таким образом, при помощи указанных методов вы сможете точно и гибко управлять именем и содержимым ассоциативных массивов в ZSH, что позволяет создавать более модульные и динамичные оболочечные скрипты.