Wie analysiere und konvertiere ich eine INI-Datei in Bash-Array-Variablen?

12

Ich versuche, eine INI-Datei in Bash-Array-Variablen zu konvertieren. Die probe ini ist wie folgt:

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

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

so werden diese:

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

und so weiter.

Im Moment konnte ich mir nur diesen Befehl einfallen lassen

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

Ein weiteres Problem ist, dass Leerzeichen nicht =berücksichtigt werden. Ich denke, es sedist wahrscheinlich besser für diesen Job geeignet, aber ich weiß nicht, wie ich eine temporäre Variable für den Abschnittsnamen in halten und speichern soll sed.

Also eine Idee, wie das geht?

Feuerstein
quelle
Wenn es einen anderen effizienten Weg gibt, kannst du auch deine Lösung posten :)
Flint
Überprüfen Sie zur einfachen Lösung: Wie greife ich auf einen INI-Wert in einem Shell-Skript zu? bei stackoverflow SE.
Kenorb

Antworten:

10

Gawk akzeptiert reguläre Ausdrücke als Feldbegrenzer. Im Folgenden werden Leerzeichen um das Gleichheitszeichen entfernt, aber in der restlichen Zeile beibehalten. Um den Wert werden Anführungszeichen eingefügt, damit die Leerzeichen, sofern vorhanden, bei der Ausführung der Bash-Zuweisung erhalten bleiben. Ich gehe davon aus, dass die Abschnittsnamen numerische Variablen sind, aber wenn Sie Bash 4 verwenden, ist es einfach, dies anzupassen, um assoziative Arrays mit den Abschnittsnamen selbst als Indizes zu verwenden.

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

Beachten Sie, dass Sie möglicherweise auch das von Khaled angezeigte Leerzeichen entfernen möchten (nur für $ 1 und Abschnitt), da Bash-Variablennamen keine Leerzeichen enthalten dürfen.

Diese Methode funktioniert auch nicht, wenn die Werte Gleichheitszeichen enthalten.

Eine andere Technik besteht darin, eine Bash- while readSchleife zu verwenden und die Zuweisungen auszuführen, während die Datei gelesen wird. Dies declareist vor den meisten schädlichen Inhalten sicher.

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

Auch hier könnten assoziative Arrays recht einfach unterstützt werden.

Bis auf weiteres angehalten.
quelle
1
Sehr nette Info und ich mag besonders die zweite Technik, da sie die eingebaute Bash-Funktion verwendet, anstatt sich auf externe Befehle zu verlassen.
Flint
@ TonyBarganski: Das kann in einen AWK-Aufruf geändert werden, anstatt einen in einen anderen weiterzuleiten.
Bis auf weiteres angehalten.
9

Ich würde für diesen Job ein einfaches Python-Skript verwenden, da es den INI- Parser eingebaut hat :

#!/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)

und dann in 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]}

Klar, es gibt kürzere Implementierungen in awk, aber ich denke, das ist lesbarer und leichter zu pflegen.

Michał Šrajer
quelle
3
In Bash-Versionen vor 4.2 muss vor dem Füllen ein assoziiertes Array deklariert werden, z. B.print "declare -A %s" % (sec)
Felix Eve
2
Statt eval:source <(cat in.ini | ./ini2arr.py)
Bis auf weiteres angehalten.
3

Wenn Sie die zusätzlichen Leerzeichen entfernen möchten, können Sie die integrierte Funktion verwenden gsub. Sie können beispielsweise Folgendes hinzufügen:

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

Dadurch werden alle Leerzeichen entfernt. Wenn Sie Leerzeichen am Anfang oder Ende des Tokens entfernen möchten, können Sie verwenden

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);
Khaled
quelle
Coole Tricks. Wusste nicht, dass es so eine eingebaute Funktion gibt :)
Flint
0

Hier ist eine reine Bash-Lösung.

Dies ist eine neue und verbesserte Version dessen, was chilladx gepostet hat:

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

Für eine wirklich einfache anfängliche Beispiel folgen: Wenn Sie diese herunterladen, kopieren Sie einfach die Dateien bash-ini-parser, und scripts/file.iniin das gleiche Verzeichnis, dann ein Client - Testskript das Beispiel erstellen mit I unten als auch nach dem gleichen Verzeichnis zur Verfügung gestellt haben.

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

Hier sind einige weitere Verbesserungen, die ich am Bash-Ini-Parser-Skript vorgenommen habe ...

Wenn Sie ini-Dateien mit Windows-Zeilenenden sowie mit Unix lesen möchten, fügen Sie diese Zeile der Funktion cfg_parser unmittelbar nach derjenigen hinzu, die die Datei liest:

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

Wenn Sie Dateien mit eingeschränkten Zugriffsberechtigungen lesen möchten, fügen Sie diese optionale Funktion hinzu:

# 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  
}
BuvinJ
quelle
Musste wegen abstimmen chmod 777. Während es sich bestenfalls um eine zwielichtige Übung handelt, ist es sicherlich nicht erforderlich, die INI-Datei ausführbar zu machen. Ein besserer Ansatz wäre, sudodie Datei zu lesen und nicht mit den Berechtigungen herumzuspielen.
Richlv
@ Richlv Ok. Ich freue mich über die Erklärung zur Abstimmung. Aber das ist ein winziger Teil davon, der für die Beantwortung der Frage insgesamt von untergeordneter Bedeutung ist. Die "Antwort" ist der Link: github.com/albfan/bash-ini-parser . Anstatt die gesamte Sache abzustimmen, für das, was bereits als optionale Wrapper-Funktion bezeichnet ist, hätten Sie eine Bearbeitung vorschlagen können.
BuvinJ
0

Unter der Voraussetzung, dass Pythons ConfigParser verfügbar ist, kann man eine Shell-Hilfsfunktion wie die folgende erstellen:

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

$IFACEund $paramsind der Abschnitt bzw. der Parameter.

Dieser Helfer erlaubt dann Anrufe wie:

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

Hoffe das hilft!

Matthias Dieter Wallnöfer
quelle
0

Wenn Sie Git zur Verfügung haben und mit der Einschränkung zufrieden sind, Unterstriche in den Schlüsselnamen nicht verwenden zu können, können Sie es git configals allgemeinen INI-Parser / -Editor verwenden.

Es behandelt das Parsen des Schlüssel / Wert-Paares von =und verwirft unbedeutende Leerzeichen. Außerdem erhalten Sie Kommentare (sowohl ;als auch #) und geben Nötigung grundsätzlich kostenlos ein. Ich habe .iniunten ein vollständiges Arbeitsbeispiel für die Eingabe und die gewünschte Ausgabe des OP (Bash-assoziative Arrays) beigefügt .

Gegeben ist jedoch eine solche Konfigurationsdatei

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

[section2]
    anothersetting = 42

… Vorausgesetzt, Sie brauchen nur eine schnelle und schmutzige Lösung und sind nicht mit der Idee verbunden, die Einstellungen in einem assoziativen Bash-Array zu haben.

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

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

Dadurch werden Umgebungsvariablen erstellt, die sectionname_variablenamein der aktuellen Umgebung benannt sind. Dies funktioniert natürlich nur, wenn Sie darauf vertrauen können, dass keiner Ihrer Werte jemals einen Punkt oder ein Leerzeichen enthält (siehe unten für eine robustere Lösung).

Andere einfache Beispiele

Abrufen beliebiger Werte mithilfe einer Shell-Funktion zum Speichern der Eingabe:

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

Ein Alias ​​wäre auch hier in Ordnung, aber diese werden normalerweise nicht in einem Shell-Skript [ 1 ] erweitert, und Aliasnamen werden von Shell-Funktionen "für fast jeden Zweck" [ 2 ] gemäß der Manpage von Bash abgelöst .

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

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

Mit dieser --typeOption können Sie bestimmte Einstellungen als Ganzzahlen, Boolesche Werte oder Pfade "kanonisieren" (automatisch erweitern ~):

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

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

Etwas robusteres Quick-and-Dirty-Beispiel

Stellen Sie alle Variablen in mytool.iniwie SECTIONNAME_VARIABLENAMEin der aktuellen Umgebung zur Verfügung, und behalten Sie dabei das interne Leerzeichen in den Schlüsselwerten bei:

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

Was der sed-Ausdruck auf Englisch tut, ist

  1. Finden einer Gruppe von Nicht-Punkt-Zeichen bis zu einem Punkt, wobei man sich daran erinnert, dass \1dann
  2. eine Reihe von Zeichen bis zu einem Gleichheitszeichen finden, sich daran erinnern, als \2, und
  3. Suche nach allen Zeichen nach dem Gleichheitszeichen als \3
  4. schließlich in der Ersatzzeichenfolge
    • Der Abschnittsname + Variablenname wird in Großbuchstaben geschrieben
    • Der Wertteil wird in doppelte Anführungszeichen gesetzt, wenn er Zeichen enthält, die für die Shell eine besondere Bedeutung haben, wenn er nicht in Anführungszeichen gesetzt wird (wie Leerzeichen).

Die Sequenzen \Uund \Ein der Ersetzungszeichenfolge (die den Teil der Ersetzungszeichenfolge in Großbuchstaben angibt) sind GNU- sedErweiterungen. Unter MacOS und BSD verwenden Sie einfach mehrere -eAusdrücke, um den gleichen Effekt zu erzielen.

Der Umgang mit eingebetteten Anführungszeichen und Leerzeichen in den Abschnittsnamen (was git configerlaubt) wird dem Leser als Übung überlassen.:)

Verwenden von Abschnittsnamen als Schlüssel in einem assoziativen Bash-Array

Gegeben:

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

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

Dies führt zu dem vom OP gewünschten Ergebnis, indem einfach einige der Captures im sed-Ersetzungsausdruck neu angeordnet werden. Ohne GNU sed funktioniert dies problemlos:

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

Ich gehe davon aus, dass das Zitieren einer realen .iniDatei einige Probleme mit sich bringen könnte , aber es funktioniert für das angegebene Beispiel. Ergebnis:

declare -p {session,path}
# result:
# declare -A session=([barfoo]="bar" [foobar]="foo" )
# declare -A path=([barfoo]="/some/path" [foobar]="/some/path" )
TheDudeAbides
quelle