Крім асоціативних масивів, існує кілька способів досягнення динамічних змінних у Bash. Зауважте, що всі ці методи становлять ризики, про які йде мова в кінці цієї відповіді.
У наступних прикладах я припускаю, i=37
що ви хочете псевдонім змінної, названої var_37
початковим значенням lolilol
.
Метод 1. Використання змінної “pointer”
Ви можете просто зберегти ім'я змінної в непрямій змінній, не на відміну від вказівника C. Потім Bash має синтаксис для читання aliase змінної: ${!name}
розширюється до значення змінної, ім'я якої - значення змінної name
. Ви можете мислити це як двоступеневе розширення: ${!name}
розширюється до $var_37
, яке розширюється до lolilol
.
name="var_$i"
echo "$name" # outputs “var_37”
echo "${!name}" # outputs “lolilol”
echo "${!name%lol}" # outputs “loli”
# etc.
На жаль, не існує відповідного синтаксису для модифікації aliase змінної. Натомість ви можете домогтися завдання одним із наступних прийомів.
1а. Призначення с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 ''
дозволяє задавати значення декількох рядків. Через цю останню опцію команда повертається з ненульовим кодом виходу.
Зауважте, що оскільки ми використовуємо тут-рядок, до цього значення додається символ нового рядка.
Недоліки:
- дещо незрозумілий;
- повертається з ненульовим кодом виходу;
- додає новий рядок до значення.
1с. Призначення с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!
Джерело: