Pull to refresh

Comments 53

У меня есть подозрение, зачем народ хочет динамических переменных. Причём, в отрыве от bash (и привязки к конкретным средствам разработки вообще).

Эти дивные люди хотят всех ништяков ООП без ООП. Учиться создавать иерархию объектов, которая не рассыпается при любой попытке добавить что-то новенькое, - долго. А кодить хочется уже сейчас. Вот и возникает искушение не мудохаться с нэймспэйсами, строгой типизацией данных и прочими тугоусвояемыми для гуманитариев нюансами, а накалдырить простенький цикл, который и названия переменных соберёт, и задекларирует их как PHP не глядя, и обработает не оглядываясь на то, вернулся нам bool, голый текст или ещё чего-нибудь. В надежде, что всё скомпилируется как надо и будет работать, как ожидалось.

Но оно физически не будет работать как надо. Глупый интерпретатор не умеет думать ни за кодера, ни за пользователя.

Если его надурить и попытаться таки сделать своё грязное дело - в лучшем случае будет абракадабра в ответ на любые входящие данные. Потому, что данные обрабатываются бессистемно, безтипово, и любой неожиданный символ на любом этапе поставит ожидаемый процесс обработки с ног на голову.

В худшем - мы тупо загадим себе память колоссальным количеством бессмысленных переменных. Чехарду в которых потом нам же и дебажить. А такую бессистемную мешанину дебажить ОЧЕНЬ больно...

Я уже лет восемь топчусь на этом этапе. Слишком туп для ООП, слишком аккуратен для такого шлакокода. Но не все настолько хорошо себя знают...

Некоторые задачи нельзя решить на других языках потому, что этих языков на целевой системе может не быть, и ставить их ради пары скриптов это лютый оверкил. А bash есть везде, а если его нет, то есть busybox какой-нибудь. И этого достаточно для любой вменяемой автоматизации. Даже под виндой.

А так, да - динамические имена переменных нужны именно в тех случаях, когда задача требует объектов, а объектов нет.

Хммммммм.

С одной стороны, Java есть везде, от SMORT-кофейников до суперкомпьютеров.
.net в том или ином виде доступен на любой пользовательской станции.
А если хочется чего-то совсем легковесного - думаю, более продвинутые пользователи Линукса добавят ещё 1-3 варианта именно легковесных.

С другой стороны, если задача требует прям-таки вменяемой автоматизации - это уже задача не для рядового пользователя (априори способного в таких случаях только стрелять по ногам).
Если задача при этом ещё и требует натянуть ООП на условия/окружение, в которых ООП по каким-то причинам использовать нельзя, - то, боюсь, тут проблема не в условиях/окружении, а либо с целеполаганием, либо с возжелавшим странного пользователем...

С третьей стороны, если такая задача появилась раз - появится и два, и три.
И взвалившему на себя такую экзотическую задачу человеку не помешало бы не плодить странный код (которого и так в избытке) и не вырабатывать у себя вредные привычки (успеется), а сразу озаботиться решением задачи по уму...

...

Пару месяцев назад тут как-то шутили:

И пафосно-натужно доказали, что даже такой изврат сделать - МОЖНО!

Однако, я не припомню, чтобы хоть для какого-то целеполагания, кроме как brain flex'а и, может быть, вечно бдящих специалистов по безопасности, это оказалось пригодно...

Да, с одной стороны сборка кибика Рубика методом 'разборки и сборки в нужной последовательности' выглядит просто и привлекательно. С другой, какой там рекорд скорости сборки КР? Несколько секунд? Т.е. если действительно УМЕЕШЬ собирать то получается гораздо проще и быстрей.

Именно. Но мы же не требуем от мастера вязания крючком, чтобы он КР собрал быстро. Хотя у него тоже руки очень скилловые.

А с каких порт администратор сервера стал "рядовым пользователем"? И зачем ему тащить Java на сервер ради вспомогательных скриптов?

Ну не рядовым, тут я погорячился, Вы правы. Но уж точно не очень скилловым. Спросить проще, чем докопаться самому, - и на одних вопросах скилл не так уж и хорошо прокачивается...

Лишние зависимости же - ну, ладно, допустим именно на этом хосте Java не предвидится абсолютно ни в каком виде. Ужли там весь сервер держится на голом баше, совсем без более пригодных для таких операций пакетов?

Не верю.

Не знаю причем тут ООП, но вот после perl, очень не хватает ассоциативных массивов. Хорошо что они уже появились в bash. Плохо что их нет в Posix shell

eval существует со времён до появления 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-ю способами. Но мне так было проще.

eval предлагает выстрелить себе в ногу в случае наличия в записываемых данных кавычек и спецсимволов. Запись через eval в переменную строку с символами $"'\ это тот ещё квест.

У вас очень очень сложный пример для 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

$ 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

Ну, да. Но мы-то изначально захотели встать на путь Хаоса Неделимого БЕЗ каких-то оговорок о производительности.

То есть, мы изначально пытаемся покидать в кучу и обработать уйму данных абсолютно без предварительной обработки - ожидать от такого процесса сверхбыстрого завершения (да в не предназначенном для этого стэке) немного опрометчиво.

Там выше мне пеняли, что не везде есть возможность или желание впереть ради одной такой манипуляции аж Java. Но будет ли она одна? И если так хочется перформанса и удобства без лишних зависимостей - ну уж SQL то в том или ином виде на хосте быть должен... Хотяаааа... не помню, не знаю, можно ли прямо в нём такое отчебучить, не скатываясь в ещё более тяжкий изврат...

(%!., куда я лезу со своим скиллом как у молодого окуня...)

Если вы хотите скорость, зачем вам баш?
Или я не понимаю этого примера вообще.

А что на bash'е модно писать кое-как, работает и ладно? А чем остальные ЯП хуже? Внедряйте и там этот подход;)

на bash нужно писать так, как оно будет работать в вашем окружении.

Но не везде есть баш последней версии с поддержкой ассоциативных массивов.

Во-вторых, часто для совместимости, нужно писать не на bash а на posix shell, а очень многиепочему-то путают эти две вещи. И да, posix shell достаточно популярен, чтобы задумываться.

Статья интересная, но посыл, предпосылки и выводы в корне неверные.

Динамические переменные нужны, например, чтобы возвращать несколько переменных из функций. Или для инициализации переменных в функции, которая на знает имя переменной. Т.е. в функцию передается имя переменной, а она никуда получает их значения и записывает результат в указанные переменные. В других языках для этого есть объекты, ссылки т всякие структуры, а в bash приходится как-то так.

Это, как ни странно, весьма часто нужно, чтобы не дублировать код. Например, у меня читается какой-то набор файлов конфигурации. Я делаю функцию, которая выполняет эту задачу, но в какие переменные положить результат? Ещё всего передать имена переменных в функцию и использовать её потом в самых разных местах.

Другой частый случай, когда даже ужасный "var$i" может понадобиться, это имитация объектов. Например, скрипт должен одновременно работать с несколькими конфигурациями (у меня он контейнерами рулит). Для этого нужно уметь одинаковым способом читать и обрабатывать несколько наборов переменных. А как это сделать без дублирования кода? Используя префиксы переменных! В выполняющую действие функцию передается префикс, а она уже сама читает/пишет все нужные переменные.

Почему не массив? Потому, что мне нужно имея префикс USER получить из функции переменные USER1_FOLDER1_NAME, USER1_FOLDER1_TIMESTAMP и т.п, где номер изменяется, и я не знаю сколько там этих фолдеров и юзеров будет. Как вы такое решать будете? Через ассоциативные массивы? Можно, но и без того немалый уровень ада в скрипте становится только больше.

Прочее не eval? Потому, что локальные/глобальные переменные и спецсимволы с кавычками в значениях. Это и глюки и уязвимости.

Например, писал я скрипт, которые делал управление правами пользователей. Пользователи могут дергать скоро через ssh, но я хочу чтобы у каждого пользователя был свой набор разрешенных действий, да, ещё и с иерархией, ролями и читаемый из нескольких файлов конфигурации. Отлично решается через префиксы переменных. Вот, там это всё в полную меру и поменялось.

Для этого отлично подойдёт ассоциативный массив.

А чтобы вернуть множество значений из функции и раскидать по переменным?

Тут уже вопрос общей архитектуры скрипта, может это и не нужно?

А если вам конфигурацию, например нужно прочитать и инициализировать? А если несколько конфигураций? А если функция в нескольких скриптах используется - каждый раз заново переписывать?

Эм, заметка была про массивы и переменные а не про конфигурацию, инициализацию и функции...
А если функция(и) в нескольких скриптах используется, делают "библиотеку(и)" функций и сорсят в скрипты.

И как вы потом из этих скриптов будете множество значений возвращать-то? Вот тут вам и понадобятся динамические переменные. Муа-ха-ха!

Куда возвращать? Зачем? Мы явно о разных вещах говорим. Напомню начальный посыл статьи, на всякий случай. Не надо создавать переменные типа var$i="$data" ведь есть готовый аналог arr[$i]="$data"

Вы говорите "не надо создавать" и предлагаете аналог, но игнорируете случаи, когда "аналог" совершенно не подходит, хотя именно эти случаи и составляют бОльшую часть танцев с динамическими именами переменных.
чтобы говорить, что-то не нужно, нужно сначала выяснить зачем оно используется. Потому, что нужна не какая-то абстрактная замена, а замена для конкретных случаев использования. А эти случаи - возврат множества переменный из библиотечный функций, либо получение на bash функционала структур/классов.

Мне наверно фантазии не хватает, не могу придумать ни одного примера где var$i справился бы лучше arr[$i] учитывая что $i может быть равно "some_thing" а не только лишь 0, 1, 2, 3...
Приведете пример?
Ну и в конце заметки я так и написал что в каких-то интересных случаях это вероятно работает но точно не для переделывания массива.

Представьте, что вам нужно из функции вернуть набор данных, куда входят несколько разных полей и массивов переменной длинны. И так, чтобы это потом использовать удобно было.
Как?
Один из вариантов - передавать префикс переменной, а функция будет создавать или использовать множество переменных с общим префиксом и разными суффиксами.
Например, вы передаёте в функцию "USER", а она созадёт переменные "USER_NAME", "USER_DIR", "USER_IP", и массив переменной длины "USER_DATA_DIR" с путями к данным пользователя. Получается что-то вроде объекта. В следующий раз вы можете захотеть передать туда "ADMIN" и получить информация по администратору и т.п.
Потом вы это префикс можете другим функциям передавать и они будут с этими данными работать. Причём, не просто работать, в изменять хранимые там данные.
Вы можете попытаться это сделать на словарях, но возникнут сложности на моменте "массив в массиве" и большие проблемы с передачей этого всего в другие функции.

Это не отвечает на вопрос: "...где var$i справился бы лучше arr[$i]?" Скорее наоборот, вот тут:
...и массив переменной длины "USER_DATA_DIR" с путями к данным пользователя...
Вы говорите что создаете массив, зачем?) Не удобнее ли насоздавать переменных "USER_DATA_DIR_FILE_$i"?)

Правильно. А теперь представьте, что часть "USER_DATA" этих переменных заранее неизвестна и переменная Т.е. вам нужно несколько таких групп данных делать - аналоги объектов, как тут уже говорилось.
Т.е. "USERxxx_DAT_DIR_FILE_xxx".
И вот тут-то вам и нужна динамическая генерация имён переменных и массивов внутри функции.

Массив нельзя экспортировать и передать в другую программу.

Не обязательно экспортировать, передайте через аргументы:
script.sh "${arr[@]}"

А теперь с полями, которые представляют собой массивы переменной длинны.

Массивы... ИМХО это все вытекает из архитектурных проблем. В нормальном процессе, разработанном с учетом существующих возможностей и ограничений, не должно возникать такой необходимости.

Но вы так и не предложили решения. Вы считаете, что нет задач, где нужно из функции возвращать наборы данных, включающие в себе массивы, а любой код, где это требуется, имеет неверную архитектуру? Смело.

Сложно обсуждать абстрактный код в вакууме, но переделывание массива в переменные а потом обратно, это плохая архитектура.

Обратно никто ничего не переделывает. Так и используют.

Мне кажется всё намного проще: люди хотят через переменные окружения прокинуть массив в вызываемую программу.

Ну. Допустим.

Я как полный баран в программировании всё равно вижу не менее одного варианта, как это сделать... правильно или неправильно, хз, но уж точно без насилия над здравым смыслом.

Сунуть в переменную окружения стринг, в вызываемой программе распарсить.

При сколь-либо адекватной организации процесса передачи, что в программе-доноре стринги, что в программе-адресате какие-то обработчики наверняка есть. Да и в случае какого "неожиданного поведения" потом разбираться легче, и лишние операции в лишней прослойке гонять не нужно.

Если же по каким-то причинам ожидается, что массив МОЖЕТ и ДОЛЖЕН измениться посреди обработки (т. е. одной или даже несколькими переменными окружения тут не отделаешься либо надо слишком часто забирать её значения и постоянно пересверять) - и это почему-то НОРМАЛЬНО, то тут надо брать диаграммы логики работы и хорошо-хорошо думать, сначала как до такого докатились, потом как от этого избавиться.

Наверное, я глупость написал. Но я правда не понимаю, как так то. Какое-то пояснение тут определённо поможет впредь чушь не пороть.

через переменную окружения можно прокинуть json с любой структурой, лишь бы влез по памяти =)

Джейсоны в чистом баше парсить дело такое себе. Jq есть, да.

Наверное, существуют системы, где есть bash, принципиально отсутствует python/perl/php, и одновременно есть необходимость в подобных извращениях.

Докер. Цель уложиться в как можно более мелкий образ.

Вот и выпиливается всё, что можно выпилить по максимуму.

Применительно к описанной ситуации получается наоборот впиливание. Массив уже есть, он наполнен данными и все эти данные дублируются в переменные. Зачем?

Цель уложиться в как можно более мелкий образ.

Чтобы что? Занять первое место на специальной олимпиаде по сборке мелких образов? ;) Если на этом образе будет строиться что то полезное - вряд ли+- 10 мб будет иметь значение. А стоимость поддержки - будет.

Чтобы меньше денег платить вестимо.

10МБ может и немного, а когда этих докеров тысяци то копеечная экономия неплохо экономит средства.

Когда их тысячи – эти 10мб лежат в слое базового образа N (число нод) раз.

Очень часто программа Python оказывается сильно больше скрипта на bash. К тому же она будет не одним файлом, а несколькими (либо отдельные приседания нужно).
Bash очень крут при работе с пайпами и прочим. Питоновская программа для жонглирования процессами и пользователями получилась намного (раз в 10) больше и требовала специальной сборки, чтобы представлять собой один файл. И её нельзя было быстро и криво поправить прямо в момент использования (ибо упаковано). Но сделать на питоне несомненно можно намного более крутые штуки.

И на целевой системе может не быть достаточно свежей версии Python. Или наоборот, прошло пять лет и скрипт использует что-то из depricated. Bash же отлично запускает скрипты 15-летней давности.

Очень часто программа Python оказывается сильно больше скрипта на bash.

скрипта однострочника. Если программа делает какую то обработку данных, то на bash, скорее всего, это будет простыня кода. С зависимостями от внешних программ.

на целевой системе может не быть достаточно свежей версии Python

Если система протухшая outdated, значит активной разработки под неё не ведётся. Закопайте стюардессу ;) Она 10 лет жила без этой программы.

Сказочно далеки вы от народа и энтерпрайза...

В каждом монастыре свой дресс-код ;) Кому-то велят запиливать новые фичи в старую систему? Штош, таков путь.

bash по своей сути ОБЯЗАН пользоваться внешними программами.
У питона есть тысячи библиотек. У bash - вся мощь gnu tools.
Поэтому обработка данных в bash - это совершенно если делается вызов длинного однострочника или даже многострочника с sed или awk, с помощью которых вполне быстро и удачно можно порешать множество задач. И вы можете даже удивиться как это реально быстро и просто

На баше в результате получаются такие монстроконструкции, когда самому автору через месяц проще заново написать скрипт вместо того чтобы править баг с поиском странного символа (пробела) в такой переменной где-то между 80 и 124 строчкой..

Если ничего больше кроме sh не стоит, а надо что-то сложное писать - может быть, выучить какой-нибудь Golang где все можно скомпилировать в один бинарник и засунуть в этот контейнер?

Sign up to leave a comment.

Articles

Change theme settings