В порыве альтруизма зашел на SO и наткнулся на очередной вопрос про создание переменных с динамическими именами. Вопросы про динамические переменные всплывют на SO довольно часто. На все подобные вопросы я отвечаю стандартно, используйте массив. Но в данном вопросе в переменные предлагалось переделать массив. И тут у меня бомбануло…
Это просто за гранью добра и зла, если бы человек не знал о существовании массивов в bash’е, можно было бы понять(и простить) желание создавать динамические переменные как-то так:
for i in {0..3}; do var$i="$some_data" # это не работает done
Но вопрошатель знает про массивы т.к. предлагает переделать в динамические переменные массив. Два слова пульсируют у меня в голове: зачем и как. Как!? Зачем?! Что ты делаешь? Остановись! Это уже больше слов, но я уже себя не контролирую! Массив это по сути и есть динамическая переменная! Вот:
for i in {0..3}; do arr[$i]="$some_data" done
Ничего не напоминает? Или это не достаточно динамично? Нужно что-то подинамичней! Нужна динамика! Тогда такой вариант:
declare -A arr names=(zero one two three) for i in {0..3}; do arr["${names[$i]}-$i"]="$some_data" done
Это ассоциативный массив, массив который в качестве идентификатора использует не число а произвольную строку. Т.е. можно хранить данные в формате «key»: «value»!
Массивы это именно то что нужно для динамического хранения информации.
Не надо создавать динамические переменные! Это не работет! Это невозможно!
На самом деле возможно, я знаю как минимум 3 способа:
-
Самый очевидный — declare
for i in {0..3}; do declare var$i="$some_data" done
-
Хак 1 — read
for i in {0..3}; do read var$i <<< "$some_data" done
-
Хак 2 — printf
for i in {0..3}; do printf -v var$i -- "$some_data" done
Ну хорошо создали мы эти переменные ну а дальше то что? Как к ним обратиться echo $var$i
? Выдаст значение $var
и $i
но не $var$i
. Это не работет! Это невозможно!
На самом деле возможно, я знаю как минимум 2 способа:
-
Опять declare
declare -n name=var$i echo $name
Тут надо пояснить, declare с ключем -n создаст переменную «указатель» обращение к этой переменной выведет не её значение а обратится к другой переменной, чьё имя указано в значении данной переменной.
-
По сути тоже но без declare
name=var$i echo ${!name}
Тот же эффект.
Ладно, это возможно. Но уж точно это не на столько удобнее массива чтобы пользоваться этим вместо массива. И тем более переделвать готовый массив в это. Я в любом месте скрипта могу сказать:
arr[234]="$cool_data"
Это создаст массив arr и поместит в ячейку с номером(индексом) 234 мои данные! Затем я обращусь к ним так:
echo "${arr[234]}"
С ассоциативным массивом несколько «сложнее», его обязательно надо объявить при помощи declare -A arr
и только после этого можно сказать:
arr[description1]="$cool_data"
И затем обратиться к созданной ячейке так:
echo "${arr[description1]}"
А теперь представим на секундочку что у нас нет массивов, только переменные(welcome to sh). Как лупануть по всем?
for i in $var1 $var2 $var3 ...; do echo $i done
Уже на 3-й начинает подташнивать, а если их 100? То ли дело массив:
for item in "${arr[@]}"; do echo "$item" done
A c ассоциативным массивом можно так:
for key "${!arr[@]}"; do value=${arr[$key]} echo "$key: $value" done
Но у ассоциативных массивов есть особенность. Сначала возьмём обычный массив:
arr=( come get some ! )
$ printf '%s ' "${arr[@]}" '|' "${!arr[@]}" come get some ! | 0 1 2 3
Значения выводятся по порядку. Теперь создадим ассоциативныи массив:
declare -A arr=( [will]=come [you]=get [dare]=some [?]=! )
$ printf '%s ' "${arr[@]}" '|' "${!arr[@]}" ! get come some | ? you will dare
Результат может удивить т.к. последовательность не совпадает. Почему? Потому что вот.
Массив позволяет делать срезы, т.е. выбрать не все а только некоторые ячейки, например так:
$ printf '%s ' "${arr[@]:1:2}" get some
Это можно использовать в «таблицах», запишем массив вот так:
arr=( # столбец 1 2 3 4 строка come get some \! # 1 will you dare \? # 2 make some noise \! # 3 ) cn=4 # кол. столбцов ln=3 # кол. строк
Теперь с помощью вот такой нехитрой функции мы можем получить любую строку.
line(){ echo "${arr[@]:$(( ($1-1) * cn )):$cn}"; } $ line 2 will you dare ?
Со столбцами немного сложней, придется использовать цикл:
clmn(){ for ((i=$(($1-1)); i<$((ln*cn)); i+=$cn)); { echo "${arr[@]:$i:1}"; }; } $ clmn 2 get you some
Теперь попробуем выбрать ячейку указав номер строки и столбца:
both(){ echo "${arr[@]:$(( (($1-1) * cn) + ($2-1) )):1}"; } $ both 2 3 # 2-я строка, 3-й столбец dare
Ассоциативный массив можно использовать для сортировки(удаления дублей) как-то так:
unsorted=( one one one one two two two three ) declare -A sorted for item in "${unsorted[@]}"; { ((sorted[$item]++)) } printf '%s ' "${unsorted[@]}" one one one one two two two three $ printf '%s ' "${!sorted[@]}" two three one for key in "${!sorted[@]}"; { printf '%s(%s) ' $key "${sorted[$key]}"; } two(3) three(1) one(4)
И еще много всяких штук можно придумать с массивами.
Попробуйте провернуть всё это используя динамические переменные!?
Вероятно это возможно но я не знаю способов и даже думать не хочу об этом.
Расставим точки над Ё.
Когда хочется создать динамическую переменную, используйте массив!Динамические переменные существуют и в каких-то экзотических случаях их можно использовать.
Тут еще много массивного bash’а: sshto, kube-dialog, piu-piu
Творите, выдумывайте, пробуйте!)
Лайки, пальцы.
ссылка на оригинал статьи https://habr.com/ru/post/685766/