LINUX.ORG.RU
ФорумAdmin

bash парсинг конфига и вопросы по «подстановкам»

 


0

1

Здравствуйте, коллеги.

Возникла задачка на из bash скрипта распарсить конфигурационный файл и, соответственно, проинициализировать свои переменные на основе данных конфига.

Формат конфига простой ка мычание: ключ = значение. Т.е. разделителем служит знак =. Что слева от него - ключ, справа - значение.

Понимаю, что на python это сделать как 2 пальца об асфальт, но, в силу ряда причин, приходится башить.

Тем не менее, на вооружение, я взял пистоновский подход:

declare -A conf

default_conf(){
 conf['user']=user
 conf['passwd']=''
 # ...
}

Т.е. инициализирую dictionary значениями по умолчанию, которые, в дальнейшем, будут проиницилизированы повторно, но уже данными из конфига.

Наверное мне проще задать вопрос на примере

load_conf(){
 # $1 - conf file
 [ -f $1 ] || return 1
 local l lines var param
 # Загружаем конфигурационный файл без коментариев (# ...) и
 # пустых строк
 IFS=$'\n'
 lines=($(awk -F# '{print $1}' | grep -v '^\s*$'))
 for l in ${lines[@]}; do
  var=${l%%=*}   
  param=${l#*=}
  # ...
 done
}

Вот, кстати, тут и возник вопрос по «подстановкам». Инициализацию var и param я подглядел, но до конца не понял.

Если не сложно, то ткните носом где о подобном можно почитать

Дальше, по идее, нужно проверять $var на наличие такого ключа в conf, но я не знаю как это сделать.

В интернете советуют проверять наличие ключа в словаре следующим образом:

[ -n "${conf[$var]}" ] && ${conf[$var]}=$param

но это как-то не верно, на мой взгляд. Ведь в словаре может быть ключ с пустым значением, как, например, в функции default_conf инициализируется conf[‘passwwd’]='', т.е. инициализируется пустым значением по умолчанию. Тут прилетает из конфига passwd = 12345 и скрипт отбросит этот пароль, т.к. в словаре пароль инициализирован пустым значением.

Можно, конечно, пойти от обратного и сразу инициализировать словарь значениями из файла, а потом уже проверять ключи на наличие, но так в словарь могут залететь непредусмотренные ключи с дурацкими значениями, типа:

 var='hernya'
 param='polnaya'
 conf[$var]=$param

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

UPD awk можно выбросить и загрузку сделать грепом:

grep -v '\s*#.*$\|^\s*$' $1


Последнее исправление: HighMan (всего исправлений: 4)

Формат конфига простой ка мычание: ключ = значение. Т.е. разделителем служит знак =. Что слева от него - ключ, справа - значение.

Такой конфиг можно парсить командой source т.к. это нативный синтаксис для задания шелл-переменных.

А если тебе нужно что-то сложнее то лучше писать на нормальном языке.

firkax ★★★★★
()
Ответ на: комментарий от firkax

А если тебе нужно что-то сложнее то лучше писать на нормальном языке.

Увы. Пока лишь bash или sh

Тут еще странная история. у меня в конфигурационном файле можно добавить список пакетов для установки. Просто и незамысловато:

install = pciutils usbutils

Когда считываю конфиг, то ни как не парсю все, что справа от знака ‘=’. Т.е. там обычная строка.

Водт теперь главный скрипт считывает конфг, и втыкивает эту строку в строку для apt-get

apt-get install -y ${conf['install']}

И, блин! Не работает! apt-get говорит, что pciutils и usbutils пакетов нет! Хотя, на самом деле они есть.

Взял и просто вывел на экран всю строку:

echo "apt-get install -y ${conf['install']}"
apt-get install -y pciutils usbutils

Т.е. все верно, но не работает.

Тогда решил все сделать через переменную

i="apt-get install -y ${conf['install']}"
$i
./installer.sh: line 200: apt-get install -y pciutils usbutils: command not found

Это что за хрень???

HighMan
() автор топика
Ответ на: комментарий от HighMan

Я ж говорил что сложное на шеллах лучше не писать.

Подозреваю что причина тут в том, что вот эта команда

apt-get install -y ${conf['install']}
превращается в
apt-get install -y "pciutils usbutils"

Хотя, у нормальных шеллов такое происходит только если поставить кавычки вокруг ${...}, но тут башизм с массивами, не знаю чего от него ожидать.

Чтобы проверить, поставить вместо apt-get запуск скрипта dump_args.sh и создай его таким:

#!/bin/sh

echo "arg1 = $1"
echo "arg2 = $2"
echo "arg3 = $3"
echo "arg4 = $4"
echo "arg5 = $5"

firkax ★★★★★
()
Ответ на: комментарий от firkax

Я ж говорил что сложное на шеллах лучше не писать.

Вот кусок кода из скрипта. Без купюр!

#....
i="apt-get install -y pciutils usbutils"
$i
#....
./installer.sh: line 200: apt-get install -y pciutils usbutils: command not found

Это что за нахер?

HighMan
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

Как будто $i раскрывается оборачиваясь одинарными кавычками.

Вроде, я нашел в чем проблема. И она не в скрипте!

Дело в том, что это удаленная машина с недособранной минисистемой.

На этой машине через mcedit быстро налабал следующий скрипт:

cat ttt.sh 
#!/usr/bin/env bash

i="apt-get install -y pciutils usbutils"
$i

./ttt.sh 
Reading Package Lists... Done
Building Dependency Tree... Done
pciutils is already the newest version.
The following extra packages will be installed:
  libusb
The following NEW packages will be installed:
  libusb usbutils
0 upgraded, 2 newly installed, 0 removed and 2 not upgraded.
Need to get 139kB of archives.
After unpacking 456kB of additional disk space will be used.
Get:1 http://update.altsp.su c10f/branch/x86_64/classic libusb 1.0.26-alt2:p10+305622.100.3.1@1662994398 [55.2kB]
Get:2 http://update.altsp.su c10f/branch/x86_64/classic usbutils 014-alt2:p10+289817.100.2.1@1637851778 [83.7kB]
Fetched 139kB in 0s (862kB/s)
Committing changes...
Preparing...                                                                       #################################################################################################### [100%]
Updating / installing...
1: libusb-1.0.26-alt2                                                              #################################################################################################### [ 50%]
2: usbutils-014-alt2                                                               #################################################################################################### [100%]
Done.

Все сработало.

Просто я пишу скрипт в vscode на другой машине с подключением по ssh и вот код из vscode отрабатывает не верно.

Судя по всему тут какая-то фигня с кодировками.

Минисистема самосборная и с локалями и кодировками я не заморачивался.

Русские буквицы кажет - вот и зашибись! Больше ни хрена и не надо.

Тем более, что подобные машинки с подобными системами будут работать без мониторов, клавиатур и прочее. Даже ssh на них скорее всего будет отключен.

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

Когда-то давно с этим возился, но совсем не долго и ооочень давно.

HighMan
() автор топика
Ответ на: комментарий от arrecck

Строка в кавычках не разбивается на слова по IFS. Вот это «apt-get install -y pciutils usbutils» будет интерпретироваться одним словом. Для разбиения нужен eval.

cat ttt.sh
#!/usr/bin/env bash
a="pciutils usbutils"

i="apt-get install -y $a"
$i

./ttt.sh 
Reading Package Lists... Done
Building Dependency Tree... Done
pciutils is already the newest version.
usbutils is already the newest version.
0 upgraded, 0 newly installed, 0 removed and 2 not upgraded.

Вроде все прошло без запинок. Но это на той же удаленной машине через mcedit написанный скрипт.

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от arrecck

Вокруг $i у него кавычек нет и там должно разбиться. А может быть в IFS не пробел? Хотя вроде IFS для другого.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax
cat tt.sh
#!/usr/bin/env bash

declare -A conf

conf['install']="pciutils usbutils"

a="apt-get install -y ${conf['install']}"
$a


./tt.sh
Reading Package Lists... Done
Building Dependency Tree... Done
pciutils is already the newest version.
usbutils is already the newest version.
0 upgraded, 0 newly installed, 0 removed and 2 not upgraded.

Ни хрена не понимаю!!!

Этот скрипт написан на моей машине в vscode и выполняется на той удаленной машине.

Что это за хрень такая???

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от firkax

Где-то до этого в том длинном скрипте кто-то портит конфиг шелла наверно, что он перестаёт разбивать переменные по пробелам.

Попробовал в моем основном скрипте вызвать eval apt-get …. и все заработало!

Казалось бы Эврика!

Но нет.

Не все так просто.

В главном скрипте наворочено ой-ой!

sudo -u ${conf['user']} eval hsh-install ${conf['verbose']} "${conf['install']}"

Такая заморока из-за того, что этот сраный hsh-install работает ТОЛЬКО от юзера. Потому его приходится вызываеть через sudo -u и тут…

sudo: eval: command not found

Взял и на своей машине (Fedora 37 Workstation) из под root:

sudo -u highman eval cat vpn.sh 
sudo: eval: command not found

Снова затык!

Если делать без sudo, то ни какой ругани на eval нет.

UPD Ларчик просто открывался…

eval sudo -u highman cat /home/highman/test.sh 
#!/bin/bash
#....

В главном скрипте ту монструозную конструкцию переделал на:

eval sudo -u ${conf['user']} hsh-install ${conf['verbose']} "${conf['install']}"

И все заработало!!!

Остались вопросы по башевским «подстановкам» типа ${line%%:=}, ${line#=}. Я вижу как они работают.

В первом варианте оно из строки, которая лежит в переменной line, выгрызает левую часть до ‘=’, во втором - правую часть после ‘=’, но эти записи я не понимаю.

Где можно почитать о подобной магии?

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 7)
Ответ на: комментарий от HighMan

Где можно почитать

man bash

Но всё же, ещё раз повторю, это совершенно неподходящий «язык» для таких вещей.

И то, что ты вместо выяснения причин проблемы тупо вставил eval-костыль - плохо.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Но всё же, ещё раз повторю, это совершенно неподходящий «язык» для таких вещей.

И то, что ты вместо выяснения причин проблемы тупо вставил eval-костыль - плохо.

bash используется потому что, возможно, в этих минисистемах не будет даже python.

Основной монструозный скрипт не так уж много работает с логикой и парсингом.

В основном, главный скрипт проверяет наличие файлов/директорий, создает или удаляет по необходимости, и выполняет внешние программы.

Т.е. все, как раз, для bash.

В python будет просто парсить и мутить логику, но вот в остальном он менее удобен.

Воротить внешние вызовы subprocess.run(…) можно, конечно, но это не сказать, что так уж удобно.

Можно еще написать не скрипт, а программу на С++ или даже на чистом кошерном С, но это для сильных духом)))

Костыль с eval…

А что в Linux без костылей?)))

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

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

HighMan
() автор топика
Последнее исправление: HighMan (всего исправлений: 1)
Ответ на: комментарий от HighMan

ОП, я нашёл время перечитать твой тжред, вот тебе пара советов.

  1. переделай конфиг так, чтобы он елся командой source. bash не питон, тут вся эта дроч со словарями не нужна. Просто такой конфиг:
MYSCRIPT_USER=username
MYSCRIPT_PASS=qwerty
# каменты
MYSCRIPT_INSTALL="pciutils usbutils"
  1. отладка

echo «apt-get install -y ${conf[‘install’]}»

Не делай так, это тебе не даст всей картины. Для отладки используй несложный printf (просто подставляй вот так перед командой, ниичего дополнительно не окавычивая):

printf '[%s]\n' apt-get install -y ${conf['install']}
# или
printf '[%s]\n' apt-get install -y $MYSCRIPT_INSTALL

должно вывести каждый аргумент на отдельной строке (квадратные скобки нужны, чтобы поймать лишние пробелы/переводы строк внутри отдельных аргументов), вот так:

[apt-get]
[install]
[-y]
[pciutils]
[usbutils]

как устроен питоновский sys.argv знаешь? Вот это оно.

  1. У тебя в ${conf[‘install’]} лежит СТРОКА вида «список названий пакетов, разделённых пробелами». Чтобы аптгет это прожевал тебе надо разрешить башу ту строку разрезать по пробелам и превратить в список аргументов. А для этого ${conf[‘install’]} (или $MYSCRIPT_INSTALL) нужно НЕ заключать в кавычки. Если тебе printf ‘[%s]\n’ печатает [pciutils usbutils] - значит у тебя кавычки. Ты когда в тред писал их почему-то опускал и всех запутал. Кавычки вокруг переменный "$foobar" в баше очень важны, особенно для имён файлов и их принято вставить почти всегда, но список имён пакетов - разумное исключение.

Тогда решил все сделать через переменную

Не надо, это ужас. Никто так не делает, это делает не то, что ты думаешь (в доках на баш сказано, что происходит, но кто ж их читает). Последующие попытки «исправить» путём вливания в смесь eval ещё сильнее запутывают ситуацию. Это непонятно, неудобно, уродливо - верные признаки для любого языка программирования (кроме си++, бггг), что ты что-то делаешь не так и пора остановиться и сделать пару шагов назад.

legolegs ★★★★★
()

Прочитайте про локальные и глобальные переменные в bash, там свои особенности. В функции load_conf() вы, похоже, не возвращаете прежднее значение IFS.

но это как-то не верно, на мой взгляд.

Ну в старых bash только так и можно было. Начиная с v4.3 появился ключ ″-v″, которому нужно указывать имя, а не содержимое переменной:

[[ -v conf[$var] ]] && echo "IS SET"

awk можно выбросить

А можно весь скрипт написать на awk, в работе со строками он может оказаться получше bash.

mky ★★★★★
()
Ответ на: комментарий от mky

А можно весь скрипт написать на awk, в работе со строками он может оказаться получше bash.

К сожалению я не настолько хорошо знаю awk. Так нахватался по верхам как вывести колонку или несколько, ну еще могу, если сильно припрет, разделитель переопределить :)

Вообще, мне не очень нравится вызывать внешние процессы для отработки операций, которые возможно выполнить в основном процессе.

HighMan
() автор топика
Ответ на: комментарий от legolegs

переделай конфиг так, чтобы он елся командой source. bash не питон, тут вся эта дроч со словарями не нужна. Просто такой конфиг:

Одно дело, если бы я делал скрипт под себя (я бы и делать его поленился), но им активно будут пользоваться на производстве «великие специалисты Linux». Для них нужно спроектировать так что бы скрипт отрабатывал их криворукость.

Вообще, беспробельная конструкция типа key=value относительно нормально заходит лишь админам-линуксоидам. Нормальным людям удобнее key = value и что бы количество пробелов было не регламетированным.

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

HighMan
() автор топика
Ответ на: комментарий от HighMan

Нормальным людям удобнее key = value и что бы количество пробелов было не регламетированным.

Разве ваш скрипт это позволяет? ИМХО, вам до этого ещё ползти и ползти, особенно если решите всю работу со строками делать на bash, без sed/grep. Это, в общем то, не сложно, но объём кода растёт. Потом вам захочется опцию extglob, а потом вы узнаете, что великие спецы линукс могут экспортировать пременную BASHOPTS, и работающий везде bash скрипт должен начинаться с выставления нужных опций.

А потом ещё начнётся «как засунуть # в value», «как засунуть \n в value»...

И всё вернётся к source :)

mky ★★★★★
()
Ответ на: комментарий от HighMan

Для криворуких чем меньше возможностей и сложность поведения - тем лучше. Единственный (но очень большой) минус bash source в том, что он исполняет и команды тоже, а также то, что при про попытке присваивания переменной значения через = с пробелами имя переменной будет считаться именем команды и даст непонятное сообщение об ошибке.

anonymous
()