Помимо ассоциативных массивов, в Bash есть несколько способов достижения динамических переменных. Обратите внимание, что все эти методы представляют риски, которые обсуждаются в конце этого ответа.
В следующих примерах я буду предполагать, что i=37
и что вы хотите присвоить псевдоним переменной, var_37
чье начальное значение равно lolilol
.
Способ 1. Использование переменной «указатель»
Вы можете просто сохранить имя переменной в косвенной переменной, в отличие от указателя Си. Bash затем имеет синтаксис для чтения псевдонима переменной: ${!name}
расширяется до значения переменной, имя которой равно значению переменной name
. Вы можете думать об этом как о двухэтапном расширении: ${!name}
расширяется до $var_37
, которое расширяется до lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
К сожалению, нет никакого аналога синтаксиса для изменения псевдонима переменной. Вместо этого вы можете выполнить задание с помощью одного из следующих приемов.
1a. Назначение сeval
eval
это зло, но это также самый простой и самый портативный способ достижения нашей цели. Вы должны тщательно избегать правой части задания, так как оно будет оцениваться дважды . Простой и систематический способ сделать это - предварительно оценить правую часть (или использовать printf %q
).
И вы должны вручную проверить, является ли левая часть допустимым именем переменной или именем с индексом (что, если это было evil_code #
?). В отличие от всех других методов, приведенных ниже, применять его автоматически.
# check that name is a valid variable name:
# note: this code does not support variable_name[index]
shopt -s globasciiranges
[[ "$name" == [a-zA-Z_]*([a-zA-Z_0-9]) ]] || exit
value='babibab'
eval "$name"='$value' # carefully escape the right-hand side!
echo "$var_37" # outputs “babibab”
Недостатки:
- не проверяет правильность имени переменной.
eval
это зло
eval
это зло
eval
это зло
1б. Назначение сread
read
Встроенное позволяет присвоить значение переменной из которых вы даете имя, факт , который может быть использован в сочетании с здесь-строками:
IFS= read -r -d '' "$name" <<< 'babibab'
echo "$var_37" # outputs “babibab\n”
IFS
Часть и опции -r
убедитесь , что значение присваивается как есть, в то время как опция -d ''
позволяет задавать значения нескольких строк. Из-за этой последней опции команда возвращается с ненулевым кодом выхода.
Обратите внимание, что, поскольку мы используем здесь-строку, к значению добавляется символ новой строки.
Недостатки:
- немного неясен;
- возвращается с ненулевым кодом выхода;
- добавляет новую строку к значению.
1c. Назначение сprintf
Начиная с Bash 3.1 (выпущен в 2005 году), printf
встроенный модуль также может присваивать свой результат переменной, имя которой дано. В отличие от предыдущих решений, это просто работает, никаких дополнительных усилий не требуется, чтобы избежать чего-то, предотвратить расщепление и так далее.
printf -v "$name" '%s' 'babibab'
echo "$var_37" # outputs “babibab”
Недостатки:
- Менее портативный (но, хорошо).
Способ 2. Использование «ссылочной» переменной
Начиная с Bash 4.3 (выпущен в 2014 году), declare
встроенная функция имеет опцию -n
для создания переменной, которая является «ссылкой на имя» на другую переменную, во многом как ссылки на C ++. Как и в методе 1, ссылка хранит имя переменной с псевдонимом, но при каждом обращении к ссылке (либо для чтения, либо для назначения) Bash автоматически разрешает косвенное обращение.
Кроме того, Bash имеет специальное и очень запутанный синтаксис для получения значения самой ссылки, судьи сами: ${!ref}
.
declare -n ref="var_$i"
echo "${!ref}" # outputs “var_37”
echo "$ref" # outputs “lolilol”
ref='babibab'
echo "$var_37" # outputs “babibab”
Это не помогает избежать ошибок, описанных ниже, но, по крайней мере, упрощает синтаксис.
Недостатки:
риски
Все эти методы сглаживания представляют несколько рисков. Первый - выполнение произвольного кода каждый раз, когда вы разрешаете косвенное обращение (либо для чтения, либо для назначения) . Действительно, вместо имени скалярной переменной, например var_37
, вы можете использовать псевдоним массива, например arr[42]
. Но Bash оценивает содержимое квадратных скобок каждый раз, когда это необходимо, поэтому псевдонимы arr[$(do_evil)]
будут иметь неожиданные последствия ... Как следствие, используйте эти методы только тогда, когда вы контролируете происхождение псевдонима .
function guillemots() {
declare -n var="$1"
var="«${var}»"
}
arr=( aaa bbb ccc )
guillemots 'arr[1]' # modifies the second cell of the array, as expected
guillemots 'arr[$(date>>date.out)1]' # writes twice into date.out
# (once when expanding var, once when assigning to it)
Второй риск - создание циклического псевдонима. Поскольку переменные Bash идентифицируются по имени, а не по области действия, вы можете непреднамеренно создать псевдоним для себя (полагая, что это будет псевдоним переменной из входящей области). Это может произойти, в частности, при использовании общих имен переменных (например var
). Как следствие, используйте эти методы только тогда, когда вы управляете именем переменной с псевдонимом .
function guillemots() {
# var is intended to be local to the function,
# aliasing a variable which comes from outside
declare -n var="$1"
var="«${var}»"
}
var='lolilol'
guillemots var # Bash warnings: “var: circular name reference”
echo "$var" # outputs anything!
Источник: