Массивный 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 способа:

  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

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

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


ссылка на оригинал статьи https://habr.com/ru/post/685766/

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *