Comment analyser et convertir un fichier ini en variables de tableau bash?

12

J'essaie de convertir un fichier ini en variables de tableau bash. L'échantillon ini est comme ci-dessous:

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

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

alors ceux-ci deviennent:

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

etc.

En ce moment, je ne pouvais trouver que cette commande

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

En outre, un autre problème est qu'il ne prend pas en compte les espaces =. Je pense que sedc'est probablement mieux adapté pour ce travail, mais je ne sais pas comment conserver et stocker une variable temporaire pour le nom de la section dans sed.

Alors une idée de comment faire ça?

Silex
la source
S'il existe un autre moyen efficace de le faire, n'hésitez pas à publier votre solution également :)
Flint
Pour une solution simple, vérifiez: Comment récupérer une valeur INI dans un script shell? à stackoverflow SE.
kenorb

Réponses:

10

Gawk accepte les expressions régulières comme délimiteurs de champ. Ce qui suit élimine les espaces autour du signe égal, mais les conserve dans le reste de la ligne. Des guillemets sont ajoutés autour de la valeur afin que ces espaces, le cas échéant, soient préservés lorsque l'affectation Bash est effectuée. Je suppose que les noms de section seront des variables numériques, mais si vous utilisez Bash 4, il serait facile de l'adapter pour utiliser des tableaux associatifs avec les noms de section eux-mêmes comme indices.

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

Notez que vous pouvez également effectuer la suppression d'espace que Khaled affiche (sur seulement $ 1 et section) car les noms de variables Bash ne peuvent pas contenir d'espaces.

De plus, cette méthode ne fonctionnera pas si les valeurs contiennent des signes égaux.

Une autre technique consisterait à utiliser une while readboucle Bash et à effectuer les affectations pendant la lecture du fichier, en utilisant declarece qui est à l'abri de la plupart des contenus malveillants.

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

Encore une fois, les tableaux associatifs pourraient assez facilement être pris en charge.

En pause jusqu'à nouvel ordre.
la source
1
Très belle info et j'aime particulièrement la deuxième technique car elle utilise la fonction bash intégrée, au lieu de s'appuyer sur une commande externe.
Flint
@TonyBarganski: Cela peut être modifié en un seul appel AWK au lieu de passer l'un dans l'autre.
pause jusqu'à nouvel ordre.
10

J'utiliserais un script python simple pour ce travail car il a intégré l' analyseur 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)

puis en 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]}

Bien sûr, il y a des implémentations plus courtes dans awk, mais je pense que c'est plus lisible et plus facile à maintenir.

Michał Šrajer
la source
3
Dans les versions bash antérieures à 4.2, il est nécessaire de déclarer un tableau associé avant de le remplir, par exempleprint "declare -A %s" % (sec)
Felix Eve
2
Au lieu de eval:source <(cat in.ini | ./ini2arr.py)
pause jusqu'à nouvel ordre.
3

Si vous souhaitez éliminer les espaces supplémentaires, vous pouvez utiliser la fonction intégrée gsub. Par exemple, vous pouvez ajouter:

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

Cela supprimera tous les espaces. Si vous souhaitez supprimer des espaces au début ou à la fin du jeton, vous pouvez utiliser

gsub(/^ /, "", $1);
gsub(/ $/, "", $1);
Khaled
la source
Des trucs sympas. Je ne savais pas qu'il y avait une telle fonction intégrée :)
Flint
0

Voici une solution bash pure.

Il s'agit d'une nouvelle version améliorée de ce que chilladx a publié:

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

Pour un exemple initial très facile à suivre: Après avoir téléchargé ceci, copiez simplement les fichiers bash-ini-parseret scripts/file.inidans le même répertoire, puis créez un script de test client en utilisant l'exemple que j'ai fourni ci-dessous dans ce même répertoire également.

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

Voici quelques autres améliorations que j'ai apportées au script bash-ini-parser ...

Si vous voulez pouvoir lire les fichiers ini avec les fins de ligne Windows ainsi que Unix, ajoutez cette ligne à la fonction cfg_parser immédiatement après celle qui lit le fichier:

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

Si vous souhaitez lire des fichiers qui ont des autorisations d'accès restrictives, ajoutez cette fonction facultative:

# 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
la source
J'ai dû voter à cause de chmod 777. Bien qu'une pratique louches au mieux, il n'est sûrement pas nécessaire de rendre le fichier ini exécutable. Une meilleure approche serait d'utiliser sudopour lire le fichier, et non pour jouer avec les autorisations.
Richlv
@Richlv Ok. J'apprécie l'explication du vote à la baisse. Mais, c'est une toute petite partie de cela, qui est d'une importance minime en ce qui concerne la réponse à la question dans son ensemble. La "réponse" est le lien: github.com/albfan/bash-ini-parser . Plutôt que de voter contre le tout, pour ce qui est déjà une fonction de wrapper facultative, vous auriez pu suggérer une modification.
BuvinJ
0

En supposant toujours d'avoir ConfigParser de Python autour, on peut construire une fonction d'assistance shell comme ceci:

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

$IFACEet $paramsont la section respectivement le paramètre.

Cet assistant permet ensuite des appels comme:

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

J'espère que cela t'aides!

Matthias Dieter Wallnöfer
la source
0

Si Git est disponible et que vous êtes d'accord avec la contrainte de ne pas pouvoir utiliser de soulignements dans les noms de clés, vous pouvez l'utiliser git configcomme analyseur / éditeur INI à usage général.

Il gérera l'analyse de la paire clé / valeur de partout dans le =et supprimera les espaces insignifiants, plus vous obtiendrez des commentaires (les deux ;et #) et tapez la contrainte essentiellement gratuitement. J'ai inclus un exemple de travail complet pour l'entrée de l'OP .iniet la sortie souhaitée (tableaux associatifs Bash), ci-dessous.

Cependant, étant donné un fichier de configuration comme celui-ci

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

[section2]
    anothersetting = 42

… À condition que vous ayez juste besoin d'une solution rapide et sale, et que vous ne soyez pas marié avec l'idée d'avoir les paramètres dans un tableau associatif Bash, vous pouvez vous en sortir avec aussi peu que:

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

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

qui crée des variables d'environnement nommées sectionname_variablenamedans l'environnement actuel. Cela, bien sûr, ne fonctionne que si vous pouvez être sûr qu'aucune de vos valeurs ne contiendra un point ou un espace (voir ci-dessous pour une solution plus robuste).

Autres exemples simples

Récupération de valeurs arbitraires, à l'aide d'une fonction shell pour enregistrer la saisie:

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

Un alias serait OK, ici aussi, mais ceux-ci ne sont normalement pas développés dans un script shell [ 1 ], et de toute façon les alias sont remplacés par des fonctions shell "pour presque tous les usages", [ 2 ], selon la page de manuel Bash .

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

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

Avec cette --typeoption, vous pouvez "canoniser" des paramètres spécifiques sous forme d'entiers, de booléens ou de chemins (à expansion automatique ~):

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

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

Exemple rapide et sale légèrement plus robuste

Rendre toutes les variables mytool.inidisponibles comme SECTIONNAME_VARIABLENAMEdans l'environnement actuel, en préservant les espaces internes dans les valeurs clés:

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

Ce que fait l'expression sed, en anglais, c'est

  1. trouver un tas de caractères non-période jusqu'à une période, en se souvenant que comme \1, puis
  2. trouver un groupe de caractères jusqu'à un signe égal, en se souvenant que \2, et
  3. trouver tous les caractères après le signe égal comme \3
  4. enfin, dans la chaîne de remplacement
    • le nom de la section + le nom de la variable est en majuscules, et
    • la partie valeur est entre guillemets, au cas où elle contient des caractères qui ont une signification spéciale pour le shell s'ils ne sont pas entre guillemets (comme les espaces)

Les séquences \Uet \Edans la chaîne de remplacement (qui majuscule cette partie de la chaîne de remplacement) sont une sedextension GNU . Sur macOS et BSD, vous utiliseriez simplement plusieurs -eexpressions pour obtenir le même effet.

Traiter les guillemets intégrés et les espaces dans les noms de section (ce qui git configpermet) est laissé comme exercice pour le lecteur.:)

Utilisation de noms de section comme clés dans un tableau associatif Bash

Donné:

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

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

Cela produira le résultat demandé par l'OP, simplement en réorganisant certaines des captures dans l'expression de remplacement sed, et fonctionnera bien sans GNU sed:

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

Je prédis qu'il pourrait y avoir quelques défis avec la citation d'un .inifichier réel , mais cela fonctionne pour l'exemple fourni. Résultat:

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