Jak parsować i konwertować plik INI do zmiennych tablicy bash?


13

Próbuję przekonwertować plik INI na zmienne tablicy bash. Przykładowy ini jest jak poniżej:

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

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

więc stają się one:

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

i tak dalej.

W tej chwili mogłem wymyślić tylko to polecenie

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

Innym problemem jest to, że nie bierze pod =uwagę miejsc w pobliżu . Myślę, że sedprawdopodobnie lepiej nadaje się do tego zadania, ale nie wiem, jak przechowywać i przechowywać zmienną tymczasową dla nazwy sekcji sed.

Więc jakiś pomysł, jak to zrobić?


Jeśli istnieje inny skuteczny sposób, możesz też opublikować swoje rozwiązanie :)
Flint


Aby uzyskać proste rozwiązanie, sprawdź: Jak pobrać wartość INI w skrypcie powłoki? w stackoverflow SE.
Kenorb

Odpowiedzi:


10

Gawk akceptuje wyrażenia regularne jako ograniczniki pól. Poniższe eliminuje spacje wokół znaku równości, ale zachowuje je w pozostałej części wiersza. Wokół wartości dodawane są cudzysłowy, więc te spacje, jeśli istnieją, są zachowywane podczas wykonywania przypisania Bash. Zakładam, że nazwy sekcji będą zmiennymi numerycznymi, ale jeśli używasz Bash 4, łatwo byłoby to przystosować do używania tablic asocjacyjnych z samymi nazwami sekcji jako indeksami.

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

Zauważ, że możesz również chcieć usunąć spację, którą pokazuje Khaled (tylko 1 $ i sekcja), ponieważ nazwy zmiennych Bash nie mogą zawierać spacji.

Ponadto ta metoda nie będzie działać, jeśli wartości zawierają znaki równości.

Inną techniką byłoby użycie while readpętli Bash i wykonywanie przypisań podczas odczytywania pliku, przy użyciu declarektórych jest bezpieczny przed najbardziej złośliwymi treściami.

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

Ponownie, tablice asocjacyjne mogłyby być dość łatwo obsługiwane.


1
Bardzo ładne informacje, a szczególnie podoba mi się druga technika, ponieważ wykorzystuje wbudowaną funkcję bash zamiast polegać na poleceniach zewnętrznych.
Flint

@TonyBarganski: Można to zmienić w jedno wywołanie AWK zamiast potokować jeden do drugiego.
Wstrzymano do odwołania.

10

Do tego zadania użyłbym prostego skryptu python, ponieważ ma on wbudowany parser 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)

a następnie w 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]}

Jasne, w awk są krótsze implementacje, ale myślę, że jest to bardziej czytelne i łatwiejsze w utrzymaniu.


3
W wersjach bash wcześniejszych niż 4.2 konieczne jest zadeklarowanie powiązanej tablicy przed jej wypełnieniem, np.print "declare -A %s" % (sec)
Felix Eve

2
Zamiast eval:source <(cat in.ini | ./ini2arr.py)
Wstrzymano do odwołania.

3

Jeśli chcesz wyeliminować dodatkowe spacje, możesz skorzystać z wbudowanej funkcji gsub. Możesz na przykład dodać:

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

Spowoduje to usunięcie wszystkich spacji. Jeśli chcesz usunąć spacje na początku lub na końcu tokena, możesz użyć

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

Fajne sztuczki. Nie wiedziałem, że jest taka wbudowana funkcja :)
Flint

0

Oto czyste rozwiązanie bash.

To jest nowa i ulepszona wersja tego, co opublikował chilladx:

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

Do bardzo łatwe do naśladowania początkowy przykład: Po pobraniu tego, wystarczy skopiować pliki bash-ini-parser, i scripts/file.inido tego samego katalogu, a następnie utworzyć skrypt testowy klient na przykładzie mam poniżej przewidzianego do tego samego katalogu, jak również.

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

Oto kilka dalszych ulepszeń, które wprowadziłem do skryptu bash-ini-parser ...

Jeśli chcesz mieć możliwość odczytu plików ini z końcówkami linii Windows, a także Uniksa, dodaj ten wiersz do funkcji cfg_parser bezpośrednio po tej, która czyta plik:

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

Jeśli chcesz czytać pliki z ograniczonymi uprawnieniami dostępu, dodaj tę opcjonalną funkcję:

# 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  
}

Musiałem głosować z powodu chmod 777. Chociaż w najlepszym razie jest to podejrzana praktyka, z pewnością nie ma potrzeby, aby plik ini był wykonywalny. Lepszym podejściem byłoby użycie sudodo odczytu pliku, a nie do bałagania się uprawnieniami.
Richlv

@Richlv Ok. Doceniam wyjaśnienie głosowania w dół. Ale to niewielka część tego, co ma minimalne znaczenie, jeśli chodzi o odpowiedź na pytanie jako całość. „Odpowiedź” to link: github.com/albfan/bash-ini-parser . Zamiast głosować w dół całą rzecz, bo to, co już jest etykietą opcjonalnej funkcji otoki, mogło zasugerować edycję.
BuvinJ

0

Zawsze zakładając, że istnieje ConfigParser Pythona, można zbudować funkcję pomocnika powłoki:

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

$IFACEi $paramsą odpowiednio sekcją parametru.

Ten pomocnik umożliwia połączenia takie jak:

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

Mam nadzieję że to pomoże!


0

Jeśli masz dostępnego Gita i nie masz możliwości używania podkreślników w nazwach kluczy, możesz użyć go git configjako ogólnego analizatora składni / edytora INI.

Zajmie się parsowaniem pary klucz / wartość z całego =i odrzuci nieznaczne białe znaki, a także otrzymasz komentarze (zarówno ;i #), jak i wpisz przymus w zasadzie za darmo. .iniPoniżej zamieściłem pełny działający przykład danych wejściowych OP i pożądanych wyników (tablice asocjacyjne Bash).

Jednak biorąc pod uwagę taki plik konfiguracyjny

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

[section2]
    anothersetting = 42

… Pod warunkiem, że potrzebujesz tylko szybkiego i brudnego rozwiązania i nie jesteś żonaty z pomysłem posiadania ustawień w tablicy asocjacyjnej Bash, możesz uciec z tak małą ilością:

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

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

który tworzy zmienne środowiskowe nazwane sectionname_variablenamew bieżącym środowisku. Działa to oczywiście tylko wtedy, gdy możesz ufać, że żadna z twoich wartości nigdy nie będzie zawierała kropki ani spacji (zobacz poniżej bardziej niezawodne rozwiązanie).

Inne proste przykłady

Pobieranie dowolnych wartości za pomocą funkcji powłoki w celu zapisania pisania:

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

Alias ​​też byłby w tym przypadku OK, ale zwykle nie są one rozszerzane w skrypcie powłoki [ 1 ], a jednak aliasy są zastępowane przez funkcje powłoki „do prawie wszystkich celów” [ 2 ], zgodnie ze stroną podręcznika użytkownika Bash .

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

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

Dzięki tej --typeopcji możesz „kanonizować” określone ustawienia jako liczby całkowite, logiczne lub ścieżki (automatycznie rozwijane ~):

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

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

Nieco bardziej solidny przykład szybkiego i brudnego

Udostępnij wszystkie zmienne mytool.inijak SECTIONNAME_VARIABLENAMEw bieżącym środowisku, zachowując wewnętrzne spacje w kluczowych wartościach:

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

To, co robi wyrażenie sed, po angielsku, jest

  1. znajdowanie grupy nie-kropkowych znaków aż do kropki, pamiętając to jako \1wtedy
  2. znalezienie grupy znaków aż do znaku równości, pamiętając, że jako \2i
  3. znalezienie wszystkich znaków po znaku równości jako \3
  4. wreszcie w ciągu zastępującym
    • nazwa sekcji + nazwa zmiennej ma wielkie litery, oraz
    • część wartości jest cytowana podwójnie, w przypadku gdy zawiera znaki, które mają specjalne znaczenie dla powłoki, jeśli nie są cytowane (jak białe znaki)

Te \Ui \Esekwencje w ciągu zastępowania (które wielkimi literami, że część napisu zastępczego) są GNU sedrozszerzeń. W systemach macOS i BSD wystarczy użyć wielu -ewyrażeń, aby uzyskać ten sam efekt.

Postępowanie z osadzonymi cytatami i białymi znakami w nazwach sekcji (co git configpozwala) pozostawia się jako ćwiczenie dla czytelnika.:)

Używanie nazw sekcji jako kluczy w tablicy asocjacyjnej Bash

Dany:

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

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

Spowoduje to wynik, o który prosi OP, po prostu przez zmianę niektórych ujęć w wyrażeniu zastępującym sed, i będzie działał dobrze bez GNU sed:

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

Przewiduję, że mogą istnieć pewne problemy z cytowaniem rzeczywistego .inipliku, ale działa on na podany przykład. Wynik:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.