Это просто за гранью добра и зла, если бы человек не знал о существовании массивов в 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
Творите, выдумывайте, пробуйте!)
Лайки, пальцы.
Комментарии (33)
thatsme
01.09.2022 03:12+2eval существует со времён до появления bash. Динамически генерировать переменные проблемой никогда не было. Т.е. shell изначально предназначен для кодогенерации.
Для чего это нужно? Вот я сломался здесь. Кодогенерацию использую уже 25 лет, а на вопрос зачем ответить не могу. Проще?
Ну вот как пример, была когдато нужна стандартизация кикстарт файлов и pxemenu файлов, и при этом чтоб оставалась определённая гибкость.
#MANDATORY - список переменных создаваемых из аргументов командной строки и требуемых для выполнения скрипта. #Пример --hostname abc.de, превращаетша в переменную HOSTNAME содержащую abc.de #SOFT - опции с дополнительным параметром #SOFT_SINGLE - булевские флаги (появление в списке аргументов == истина) export MANDATORY="HOSTNAME MAC" export SOFT="IP GATEWAY NETMASK NAMESERVER TMPSZ ROOTSZ OPTSZ VARSZ SWAPSZ KSOUT EXCDR BONDSLAVES OS" export SOFT_SINGLE="NOIPV6 NOOPT BONDING" chk_args "$@" # магия с eval тут if [ "${IP}x" != "x" -o "${GATEWAY}x" != "x" -o "${NAMESERVER}x" != "x" ] then export MANDATORY="${MANDATORY} IP GATEWAY NAMESERVER NETMASK" export BOOTPROTO="static" unset SOFT chk_args "$@" # и тут else export BOOTPROTO="dhcp" fi
А в шаблоне кикстарт файла тоже, например для определения имён дисков и в последствии автоматического создания томов на этапе инсталляции. Т.е. генерация происходит в самом кикстарте на этапе инсталляции ...
export EXCLUDE_DRIVES=XXXEXCDRVXXX if [ "${EXCLUDE_DRIVES}x" == "x" ] then set $(list-harddrives) else set $(list-harddrives|egrep -v "${EXCLUDE_DRIVES}") fi export args=( "$@" ) let argc=$# let i=0 let drives=0 driveorder="" while [ $i -lt $argc ] do let drives++ drivename="drive${drives}" drive=${args[$i]} export ${drivename}=$drive let i+=2 if [ "${driveorder}x" == "x" ] then driveorder="$(eval echo \$$drivename)" else driveorder="${driveorder},$(eval echo \$$drivename)" fi # ..... # где-то ниже по коду echo "clearpart --all --initlabel --drives=${driveorder}" >> /tmp/part-include echo "bootloader --location=mbr --append=\"rhgb quiet\" --driveorder=${driveorder}" >> /tmp/part-include echo "zerombr" >> /tmp/part-include # ......... # где-то ниже по коду if [ $drives -gt 1 ] then pvlist=() let j=1 let i=2 while [ $j -lt $drives ] do if [ "${args[$i]}x" != "x" ] then pv="pv.0$(( j + 1 ))" echo "part $pv --size=512 --grow --ondisk=${args[$i]}" >> /tmp/part-include pvlist=(${pvlist[@]} $pv) # .... # где-то ниже по коду if [ ${#pvlist[@]} -ne 0 ] then echo "volgroup ${VGNAMEPFX}.data ${pvlist[@]}" >> /tmp/part-include if [ ${NOOPT} -eq 0 ] then echo "logvol /opt --vgname=${VGNAMEPFX}.data --fstype=xfs --size=$optsz --name=opt" >> /tmp/part-include fi
Тут вот без кодогенерации никак. Т.к. кикстарт файл создаётся из шаблона на одной машине и записывается в гит. tftp серверы, где-то там ... машина грузится получает кикстарт файл и должна динамически создать группы томов и томы файловой системы из пачки дисков. А их кол-во переменное, но группы томов и имена самих томов стандартизированы, так-же как и их размеры которые при генерации кикстарт файла могут быть изменены в определённых пределах.
Это наверняка можно всё было сделать ещё 10-ю способами. Но мне так было проще.
Sap_ru
01.09.2022 04:16eval предлагает выстрелить себе в ногу в случае наличия в записываемых данных кавычек и спецсимволов. Запись через eval в переменную строку с символами $"'\ это тот ещё квест.
saboteur_kiev
01.09.2022 15:33У вас очень очень сложный пример для eval. Ведь проще было процитировать скрипт из статьи
$ for i in {0..3}; do var$i="$some_data" # это не работает done var0=: command not found ...
и показать как он может заработать с eval
$ for i in {0..3}; do eval var$i="some_data" # это работает done $ echo $var1 some_data
vaniacer Автор
01.09.2022 18:32$ time for i in {0..100000}; do eval var$i="$i"; done real 0m1,265s user 0m1,251s sys 0m0,012s $ time for i in {0..100000}; do arr[$i]="$i"; done real 0m0,646s user 0m0,642s sys 0m0,004s
DrinkFromTheCup
02.09.2022 00:14Ну, да. Но мы-то изначально захотели встать на путь Хаоса Неделимого БЕЗ каких-то оговорок о производительности.
То есть, мы изначально пытаемся покидать в кучу и обработать уйму данных абсолютно без предварительной обработки - ожидать от такого процесса сверхбыстрого завершения (да в не предназначенном для этого стэке) немного опрометчиво.
Там выше мне пеняли, что не везде есть возможность или желание впереть ради одной такой манипуляции аж Java. Но будет ли она одна? И если так хочется перформанса и удобства без лишних зависимостей - ну уж SQL то в том или ином виде на хосте быть должен... Хотяаааа... не помню, не знаю, можно ли прямо в нём такое отчебучить, не скатываясь в ещё более тяжкий изврат...
(%!., куда я лезу со своим скиллом как у молодого окуня...)
Sap_ru
01.09.2022 04:06+1Статья интересная, но посыл, предпосылки и выводы в корне неверные.
Динамические переменные нужны, например, чтобы возвращать несколько переменных из функций. Или для инициализации переменных в функции, которая на знает имя переменной. Т.е. в функцию передается имя переменной, а она никуда получает их значения и записывает результат в указанные переменные. В других языках для этого есть объекты, ссылки т всякие структуры, а в bash приходится как-то так.
Это, как ни странно, весьма часто нужно, чтобы не дублировать код. Например, у меня читается какой-то набор файлов конфигурации. Я делаю функцию, которая выполняет эту задачу, но в какие переменные положить результат? Ещё всего передать имена переменных в функцию и использовать её потом в самых разных местах.
Другой частый случай, когда даже ужасный "var$i" может понадобиться, это имитация объектов. Например, скрипт должен одновременно работать с несколькими конфигурациями (у меня он контейнерами рулит). Для этого нужно уметь одинаковым способом читать и обрабатывать несколько наборов переменных. А как это сделать без дублирования кода? Используя префиксы переменных! В выполняющую действие функцию передается префикс, а она уже сама читает/пишет все нужные переменные.
Почему не массив? Потому, что мне нужно имея префикс USER получить из функции переменные USER1_FOLDER1_NAME, USER1_FOLDER1_TIMESTAMP и т.п, где номер изменяется, и я не знаю сколько там этих фолдеров и юзеров будет. Как вы такое решать будете? Через ассоциативные массивы? Можно, но и без того немалый уровень ада в скрипте становится только больше.
Прочее не eval? Потому, что локальные/глобальные переменные и спецсимволы с кавычками в значениях. Это и глюки и уязвимости.
Например, писал я скрипт, которые делал управление правами пользователей. Пользователи могут дергать скоро через ssh, но я хочу чтобы у каждого пользователя был свой набор разрешенных действий, да, ещё и с иерархией, ролями и читаемый из нескольких файлов конфигурации. Отлично решается через префиксы переменных. Вот, там это всё в полную меру и поменялось.
vaniacer Автор
01.09.2022 09:35+1Для этого отлично подойдёт ассоциативный массив.
Sap_ru
01.09.2022 16:56А чтобы вернуть множество значений из функции и раскидать по переменным?
vaniacer Автор
01.09.2022 18:35-1Тут уже вопрос общей архитектуры скрипта, может это и не нужно?
Sap_ru
01.09.2022 18:38А если вам конфигурацию, например нужно прочитать и инициализировать? А если несколько конфигураций? А если функция в нескольких скриптах используется - каждый раз заново переписывать?
vaniacer Автор
01.09.2022 18:48Эм, заметка была про массивы и переменные а не про конфигурацию, инициализацию и функции...
А если функция(и) в нескольких скриптах используется, делают "библиотеку(и)" функций и сорсят в скрипты.Sap_ru
02.09.2022 17:46И как вы потом из этих скриптов будете множество значений возвращать-то? Вот тут вам и понадобятся динамические переменные. Муа-ха-ха!
vaniacer Автор
03.09.2022 14:13Куда возвращать? Зачем? Мы явно о разных вещах говорим. Напомню начальный посыл статьи, на всякий случай. Не надо создавать переменные типа
var$i="$data"
ведь есть готовый аналогarr[$i]="$data"
weirded
01.09.2022 06:48+2Мне кажется всё намного проще: люди хотят через переменные окружения прокинуть массив в вызываемую программу.
DrinkFromTheCup
01.09.2022 08:20+1Ну. Допустим.
Я как полный баран в программировании всё равно вижу не менее одного варианта, как это сделать... правильно или неправильно, хз, но уж точно без насилия над здравым смыслом.
Сунуть в переменную окружения стринг, в вызываемой программе распарсить.
При сколь-либо адекватной организации процесса передачи, что в программе-доноре стринги, что в программе-адресате какие-то обработчики наверняка есть. Да и в случае какого "неожиданного поведения" потом разбираться легче, и лишние операции в лишней прослойке гонять не нужно.
Если же по каким-то причинам ожидается, что массив МОЖЕТ и ДОЛЖЕН измениться посреди обработки (т. е. одной или даже несколькими переменными окружения тут не отделаешься либо надо слишком часто забирать её значения и постоянно пересверять) - и это почему-то НОРМАЛЬНО, то тут надо брать диаграммы логики работы и хорошо-хорошо думать, сначала как до такого докатились, потом как от этого избавиться.
Наверное, я глупость написал. Но я правда не понимаю, как так то. Какое-то пояснение тут определённо поможет впредь чушь не пороть.
saboteur_kiev
01.09.2022 15:34через переменную окружения можно прокинуть json с любой структурой, лишь бы влез по памяти =)
randomsimplenumber
01.09.2022 09:15Наверное, существуют системы, где есть bash, принципиально отсутствует python/perl/php, и одновременно есть необходимость в подобных извращениях.
NN1
01.09.2022 09:19+1Докер. Цель уложиться в как можно более мелкий образ.
Вот и выпиливается всё, что можно выпилить по максимуму.
vaniacer Автор
01.09.2022 09:40Применительно к описанной ситуации получается наоборот впиливание. Массив уже есть, он наполнен данными и все эти данные дублируются в переменные. Зачем?
randomsimplenumber
02.09.2022 07:56-1Цель уложиться в как можно более мелкий образ.
Чтобы что? Занять первое место на специальной олимпиаде по сборке мелких образов? ;) Если на этом образе будет строиться что то полезное - вряд ли+- 10 мб будет иметь значение. А стоимость поддержки - будет.
Sap_ru
01.09.2022 17:02Очень часто программа Python оказывается сильно больше скрипта на bash. К тому же она будет не одним файлом, а несколькими (либо отдельные приседания нужно).
Bash очень крут при работе с пайпами и прочим. Питоновская программа для жонглирования процессами и пользователями получилась намного (раз в 10) больше и требовала специальной сборки, чтобы представлять собой один файл. И её нельзя было быстро и криво поправить прямо в момент использования (ибо упаковано). Но сделать на питоне несомненно можно намного более крутые штуки.
И на целевой системе может не быть достаточно свежей версии Python. Или наоборот, прошло пять лет и скрипт использует что-то из depricated. Bash же отлично запускает скрипты 15-летней давности.randomsimplenumber
02.09.2022 10:22-1Очень часто программа Python оказывается сильно больше скрипта на bash.
скриптаоднострочника. Если программа делает какую то обработку данных, то на bash, скорее всего, это будет простыня кода. С зависимостями от внешних программ.на целевой системе может не быть достаточно свежей версии Python
Если система
протухшаяoutdated, значит активной разработки под неё не ведётся. Закопайте стюардессу ;) Она 10 лет жила без этой программы.Sap_ru
02.09.2022 17:47+1Сказочно далеки вы от народа и энтерпрайза...
randomsimplenumber
02.09.2022 18:16В каждом монастыре свой дресс-код ;) Кому-то велят запиливать новые фичи в старую систему? Штош, таков путь.
DrinkFromTheCup
У меня есть подозрение, зачем народ хочет динамических переменных. Причём, в отрыве от bash (и привязки к конкретным средствам разработки вообще).
Эти дивные люди хотят всех ништяков ООП без ООП. Учиться создавать иерархию объектов, которая не рассыпается при любой попытке добавить что-то новенькое, - долго. А кодить хочется уже сейчас. Вот и возникает искушение не мудохаться с нэймспэйсами, строгой типизацией данных и прочими тугоусвояемыми для гуманитариев нюансами, а накалдырить простенький цикл, который и названия переменных соберёт, и задекларирует их как PHP не глядя, и обработает не оглядываясь на то, вернулся нам bool, голый текст или ещё чего-нибудь. В надежде, что всё скомпилируется как надо и будет работать, как ожидалось.
Но оно физически не будет работать как надо. Глупый интерпретатор не умеет думать ни за кодера, ни за пользователя.
Если его надурить и попытаться таки сделать своё грязное дело - в лучшем случае будет абракадабра в ответ на любые входящие данные. Потому, что данные обрабатываются бессистемно, безтипово, и любой неожиданный символ на любом этапе поставит ожидаемый процесс обработки с ног на голову.
В худшем - мы тупо загадим себе память колоссальным количеством бессмысленных переменных. Чехарду в которых потом нам же и дебажить. А такую бессистемную мешанину дебажить ОЧЕНЬ больно...
Я уже лет восемь топчусь на этом этапе. Слишком туп для ООП, слишком аккуратен для такого шлакокода. Но не все настолько хорошо себя знают...
Sap_ru
Некоторые задачи нельзя решить на других языках потому, что этих языков на целевой системе может не быть, и ставить их ради пары скриптов это лютый оверкил. А bash есть везде, а если его нет, то есть busybox какой-нибудь. И этого достаточно для любой вменяемой автоматизации. Даже под виндой.
А так, да - динамические имена переменных нужны именно в тех случаях, когда задача требует объектов, а объектов нет.
DrinkFromTheCup
Хммммммм.
С одной стороны, Java есть везде, от SMORT-кофейников до суперкомпьютеров.
.net в том или ином виде доступен на любой пользовательской станции.
А если хочется чего-то совсем легковесного - думаю, более продвинутые пользователи Линукса добавят ещё 1-3 варианта именно легковесных.
С другой стороны, если задача требует прям-таки вменяемой автоматизации - это уже задача не для рядового пользователя (априори способного в таких случаях только стрелять по ногам).
Если задача при этом ещё и требует натянуть ООП на условия/окружение, в которых ООП по каким-то причинам использовать нельзя, - то, боюсь, тут проблема не в условиях/окружении, а либо с целеполаганием, либо с возжелавшим странного пользователем...
С третьей стороны, если такая задача появилась раз - появится и два, и три.
И взвалившему на себя такую экзотическую задачу человеку не помешало бы не плодить странный код (которого и так в избытке) и не вырабатывать у себя вредные привычки (успеется), а сразу озаботиться решением задачи по уму...
...
Пару месяцев назад тут как-то шутили:
И пафосно-натужно доказали, что даже такой изврат сделать - МОЖНО!
Однако, я не припомню, чтобы хоть для какого-то целеполагания, кроме как brain flex'а и, может быть, вечно бдящих специалистов по безопасности, это оказалось пригодно...
vaniacer Автор
Да, с одной стороны сборка кибика Рубика методом 'разборки и сборки в нужной последовательности' выглядит просто и привлекательно. С другой, какой там рекорд скорости сборки КР? Несколько секунд? Т.е. если действительно УМЕЕШЬ собирать то получается гораздо проще и быстрей.
DrinkFromTheCup
Именно. Но мы же не требуем от мастера вязания крючком, чтобы он КР собрал быстро. Хотя у него тоже руки очень скилловые.
Sap_ru
А с каких порт администратор сервера стал "рядовым пользователем"? И зачем ему тащить Java на сервер ради вспомогательных скриптов?
DrinkFromTheCup
Ну не рядовым, тут я погорячился, Вы правы. Но уж точно не очень скилловым. Спросить проще, чем докопаться самому, - и на одних вопросах скилл не так уж и хорошо прокачивается...
Лишние зависимости же - ну, ладно, допустим именно на этом хосте Java не предвидится абсолютно ни в каком виде. Ужли там весь сервер держится на голом баше, совсем без более пригодных для таких операций пакетов?
Не верю.
saboteur_kiev
Не знаю причем тут ООП, но вот после perl, очень не хватает ассоциативных массивов. Хорошо что они уже появились в bash. Плохо что их нет в Posix shell