Назад | Перейти на главную страницу

Как разобрать и преобразовать файл ini в переменные массива bash?

Я пытаюсь преобразовать 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.

Есть идеи, как это сделать?

Я бы использовал простой скрипт 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]}

Конечно, в awk есть более короткие реализации, но я думаю, что это более читабельно и проще в обслуживании.

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

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

Обратите внимание, что вы также можете удалить пробелы, которые показывает Халед (только для $ 1 и раздела), поскольку имена переменных Bash не могут содержать пробелы.

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

Другой способ - использовать Bash while read цикл и выполнять назначения по мере чтения файла, используя 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

Опять же, ассоциативные массивы довольно легко поддерживаются.

Если вы хотите убрать лишние пробелы, вы можете использовать встроенную функцию gsub. Например, вы можете добавить:

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

Это удалит все пробелы. Если вы хотите удалить пробелы в начале или конце токена, вы можете использовать

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

Всегда предполагая, что у Python есть ConfigParser, можно создать вспомогательную функцию оболочки следующим образом:

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

Надеюсь это поможет!

Вот чистое решение bash.

Это новая и улучшенная версия того, что опубликовал 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  
}

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

Он будет обрабатывать парсинг пары ключ / значение из = и отбросить несущественные пробелы, плюс вы получите комментарии (оба ; и #) и приведение типов в основном бесплатно. Я включил полный рабочий пример ввода OP .ini и желаемый результат (ассоциативные массивы Bash) ниже.

Однако, учитывая такой конфигурационный файл

; 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 последовательности в строке замены (в верхнем регистре эта часть строки замены) являются GNU sed расширение. В macOS и BSD вы просто используете несколько -e выражения для достижения того же эффекта.

Работа со встроенными кавычками и пробелами в раздел имена (которые git config позволяет) остается в качестве упражнения для читателя. :)

Использование имен разделов в качестве ключей в ассоциативном массиве Bash

Дано:

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

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

Это приведет к результату, который запрашивает OP, просто переставив некоторые захваты в выражении замены 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" )