Як проаналізувати та перетворити файл ini у змінні масиву bash?


13

Я намагаюся перетворити файл ini в змінні масиву bash. Зразок ini наведений нижче:

[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

таким чином вони стають:

session[foobar]=foo
path[foobar]=/some/path
session[barfoo]=bar

і так далі.

Зараз я міг придумати лише цю команду

awk -F'=' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" $2 }'

Крім того, ще одна проблема полягає в тому, що вона не займає пробілів поруч =. Я думаю, що sed, мабуть, краще підходить для цієї роботи, але я не знаю, як утримувати та зберігати тимчасову змінну для назви розділу в sed.

То будь-яка ідея, як це зробити?


Якщо для цього є інший ефективний спосіб, не соромтеся також розмістити своє рішення :)
Флінт


Для простого рішення перевірте: Як я можу отримати значення INI в сценарії оболонки? при stackoverflow SE.
kenorb

Відповіді:


10

Gawk приймає регулярні вирази як роздільники поля. Далі виключаються пробіли навколо знака рівності, але зберігається їх у решті рядка. Котирування додаються навколо значення, тому ці пробіли, якщо такі є, зберігаються під час виконання завдання Bash. Я припускаю, що назви розділів будуть числовими змінними, але якщо ви використовуєте Bash 4, було б легко адаптувати це до використання асоціативних масивів із самими назвами розділів як індексів.

awk -F ' *= *' '{ if ($1 ~ /^\[/) section=$1; else if ($1 !~ /^$/) print $1 section "=" "\"" $2 "\"" }'

Зауважте, що ви можете також зробити пробіл, який показує Халед (лише на $ 1 і розділ), оскільки імена змінних Bash не можуть містити пробілів.

Також цей метод не працюватиме, якщо значення містять знаки рівності.

Іншою технікою було б використання while readциклу Bash та виконання завдань під час зчитування файлу, використовуючи declareякий захищений від більшості шкідливого вмісту.

foobar=1
barfoo=2  # or you could increment an index variable each time a section is found
while IFS='= ' read var val
do
    if [[ $var == \[*] ]]
    then
        section=$var
    elif [[ $val ]]
    then
        declare "$var$section=$val"
    fi
done < filename

Знову ж таки, асоціативні масиви досить легко можна підтримати.


1
Дуже приємна інформація, і мені особливо подобається друга техніка, оскільки вона використовує вбудовану функцію bash, замість того, щоб покладатися на зовнішню команду.
Кремінь

@TonyBarganski: Це можна змінити в один дзвінок AWK, а не в інший.
Призупинено до подальшого повідомлення.

10

Я використовував би простий скрипт python для цієї роботи, оскільки він вбудований в INI- аналізатор :

#!/usr/bin/env python

import sys, ConfigParser

config = ConfigParser.ConfigParser()
config.readfp(sys.stdin)

for sec in config.sections():
    print "declare -A %s" % (sec)
    for key, val in config.items(sec):
        print '%s[%s]="%s"' % (sec, key, val)

а потім у bash:

#!/bin/bash

# load the in.ini INI file to current BASH - quoted to preserve line breaks
eval "$(cat in.ini  | ./ini2arr.py)"

# test it:
echo ${barfoo[session]}

Звичайно, є більш короткі реалізації, але я думаю, що це легше читати і простіше у обслуговуванні.


3
У версіях bash до 4.2 потрібно оголосити асоційований масив перед його заповненням, наприкладprint "declare -A %s" % (sec)
Felix Eve

2
Замість eval:source <(cat in.ini | ./ini2arr.py)
Призупинено до подальшого повідомлення.

3

Якщо ви хочете усунути зайві пробіли, ви можете використовувати вбудовану функцію gsub. Наприклад, ви можете додати:

gsub(/ /, "", $1);

Це видалить усі пробіли. Якщо ви хочете видалити пробіли на початку або в кінці токена, можете скористатися

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);

Класні хитрощі. Не знав, що є така вбудована функція :)
Флінт

0

Ось чистий баш-розчин.

Це нова та вдосконалена версія публікації chilladx:

https://github.com/albfan/bash-ini-parser

Насправді легко наслідувати початковий приклад: Після завантаження цього файлу просто скопіюйте файли bash-ini-parserта scripts/file.iniв той самий каталог, а потім створіть сценарій тестування клієнтів, використовуючи приклад, поданий нижче, до того ж каталогу.

source ./bash-ini-parser
cfg_parser "./file.ini"
cfg_section_sec2
echo "var2=$var2"
echo "var5[*]=${var5[*]}"
echo "var5[1]=${var5[1]}"

Ось кілька додаткових удосконалень, які я вніс до сценарію bash-ini-parser ...

Якщо ви хочете мати можливість читати ini-файли із закінченнями рядків Windows, а також Unix, додайте цей рядок до функції cfg_parser відразу після тієї, яка читає файл:

ini=$(echo "$ini"|tr -d '\r') # remove carriage returns

Якщо ви хочете читати файли, які мають обмежувальні права доступу, додайте цю додаткову функцію:

# Enable the cfg_parser to read "locked" files
function sudo_cfg_parser {

    # Get the file argument
    file=$1

    # If not "root", enable the "sudo" prefix
    sudoPrefix=
    if [[ $EUID -ne 0 ]]; then sudoPrefix=sudo; fi

    # Save the file permissions, then "unlock" the file
    saved_permissions=$($sudoPrefix stat -c %a $file)
    $sudoPrefix chmod 777 $file

    # Call the standard cfg_parser function
    cfg_parser $file

    # Restore the original permissions
    $sudoPrefix chmod $saved_permissions $file  
}

Довелося відмовитись через chmod 777. Хоча тіньова практика в кращому випадку, напевно, не потрібно робити файл ini виконуваним. Кращим підходом було б використовувати sudoдля читання файлу, а не возитися з дозволами.
Richlv

@Richlv Гаразд. Я вдячний за пояснення проти голосу. Але, це крихітна частина цього, яка має мінімальне значення, що стосується відповіді на питання в цілому. "Відповідь" - це посилання: github.com/albfan/bash-ini-parser . Замість того, щоб голосувати за все, за те, що вже є міткою додаткової функції обгортки, ви могли б запропонувати змінити.
BuvinJ

0

Завжди, якщо у вас є ConfigParser Python навколо, можна створити функцію помічника оболонки, як це:

get_network_value()
{
    cat <<EOF | python
import ConfigParser
config = ConfigParser.ConfigParser()
config.read('network.ini')
print (config.get('$IFACE','$param'))
EOF
}

$IFACEі $paramє розділом відповідно параметром.

Потім цей помічник дозволяє здійснювати дзвінки на зразок:

address=`param=address get_network_value` || exit 1
netmask=`param=netmask get_network_value` || exit 1
gateway=`param=gateway get_network_value` || exit 1

Сподіваюся, це допомагає!


0

Якщо у вас Git доступний і все в порядку з обмеженням неможливості використовувати підкреслення в ключових іменах, ви можете використовувати git configяк аналізатор / редактор INI загального призначення.

Він буде обробляти аналіз клавіш / значень пари навколо =та відкидати незначну пробіл, плюс ви отримуєте коментарі (і ;та #) і вводите примус в основному безкоштовно. .iniНижче я включив повний робочий приклад для введення та бажаного виходу ОП (асоціативні масиви Баша).

Однак, з таким конфігураційним файлом

; mytool.ini
[section1]
    inputdir = ~/some/dir
    enablesomefeature = true
    enablesomeotherfeature = yes
    greeting = Bonjour, Monde!

[section2]
    anothersetting = 42

… За умови, що вам просто потрібне швидке і брудне рішення, і ви не одружені з ідеєю встановлення налаштувань в асоціативному масиві Bash, ви можете піти якнайменше:

eval $(git config -f mytool.ini --list | tr . _)

# or if 'eval' skeeves you out excessively
source <(git config -f mytool.ini --list | tr . _)

що створює змінні середовища, названі sectionname_variablenameв поточному середовищі. Це, звичайно, працює лише в тому випадку, якщо ви можете довіритися, що жодне з ваших значень ніколи не буде містити періоду чи пробілу (див. Нижче для більш надійного рішення).

Інші прості приклади

Отримання довільних значень за допомогою функції оболонки для збереження набору тексту:

function myini() { git config -f mytool.ini; }

Псевдонім також буде нормальним, але і вони зазвичай не розширюються в сценарії оболонки [ 1 ], і все одно псевдоніми витісняються функціями оболонки "майже для будь-яких цілей" [ 2 ], повідомляється на сторінці чоловіка Баша .

myini --list
# result:
# section1.inputdir=~/some/dir
# section1.enablesomefeature=true
# section1.enablesomeotherfeature=yes
# section2.anothersetting=42

myini --get section1.inputdir
# result:
# ~/some/dir

За допомогою цього --typeпараметра ви можете "канонізувати" певні налаштування як цілі числа, булеві або шляхи (автоматично розширюються ~):

myini --get --type=path section1.inputdir  # value '~/some/dir'
# result:
# /home/myuser/some/dir

myini --get --type=bool section1.enablesomeotherfeature  # value 'yes'
# result:
# true

Трохи більш надійний приклад швидкого і брудного

Зробіть усі змінні mytool.iniдоступними, як SECTIONNAME_VARIABLENAMEу поточному середовищі, зберігаючи внутрішнє пробіл у ключових значеннях:

source <(
    git config -f mytool.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/\U\1_\2\E="\3"/'
)

Те, що робить вираз sed, англійською мовою

  1. знаходячи купу неперіодичних символів до періоду, пам’ятаючи про те, що, як \1тоді
  2. знаходячи купу символів до знаку рівності, пам’ятаючи, що як \2і
  3. знаходження всіх символів після знака рівності як \3
  4. нарешті, у рядку заміни
    • назва розділу + ім'я змінної має верхній регістр, і
    • частина значення подвійне цитування, якщо вона містить символи, які мають спеціальне значення для оболонки, якщо вони не цитуються (наприклад, пробіл)

Послідовності \Uта \Eпослідовності в рядку заміни (який є верхній регістр цієї частини рядка заміни) є sedрозширенням GNU . Для macOS та BSD ви просто використовуватимете кілька -eвиразів, щоб досягти однакового ефекту.

Робота із вбудованими цитатами та пробілами в назвах розділів (що git configдозволяє) залишається вправою для читача.:)

Використання імен розділів як клавіш в асоціативному масиві Bash

Подано:

; foo.ini
[foobar]
session=foo
path=/some/path

[barfoo]
session=bar
path=/some/path

Це дасть результат, про який вимагає ОП, просто переставивши деякі захоплення в виразі заміни sed, і буде добре працювати без GNU sed:

source <(
    git config -f foo.ini --list \
      | sed 's/\([^.]*\)\.\(.*\)=\(.*\)/declare -A \2["\1"]="\3"/'
)

Я прогнозую, що з цитуванням реального .iniфайлу можуть виникнути певні труднощі , але це працює на наведеному прикладі. Результат:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.