Массивный BASH
В порыве альтруизма зашел на 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
Творите, выдумывайте, пробуйте!)
Лайки, пальцы.