Массивный BASH

4dbed226a942713b2ef085ad44652c13.png

В порыве альтруизма зашел на 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 способа:

  1. Самый очевидный — declare

for i in {0..3}; do
    declare var$i="$some_data"
done
  1. Хак 1 — read

for i in {0..3}; do
    read var$i <<< "$some_data"
done
  1. Хак 2 — printf

for i in {0..3}; do
    printf -v var$i -- "$some_data"
done

Ну хорошо создали мы эти переменные ну, а дальше то что? Как к ним обратиться echo $var$i? Выдаст значение $var и $i, но не $var$i. Это не работет! Это невозможно!

На самом деле возможно, я знаю как минимум 2 способа:

  1. Опять declare

declare -n name=var$i
echo $name

Тут надо пояснить, declare с ключем -n создаст переменную «указатель» обращение к этой переменной выведет не её значение, а обратится к другой переменной, чьё имя указано в значении данной переменной.

  1. По сути тоже, но без 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

Творите, выдумывайте, пробуйте!)

Лайки, пальцы.

© Habrahabr.ru