Le système de configuration

Introduction

Cette petite bibliothèque permet de gérer depuis le shell des fichiers (ou chaînes) de configuration dans un format créé à cet effet, et ressemblant fortement au format properties de java.

Le format de fichier

La syntaxe de base

# un commentaire
clé = valeur

clé2 = valeur  # reste de la ligne en commentaire

Lors de la lecture, les espaces autour du signe égal et à la fin de la valeur sont ignorées. De plus, toute ligne vide ou caractères après un # l’est aussi.

Le nom des clés est libre, seuls les espaces, tabulations, caractères #, ' , et = sont proscrits. Cela permet une certaine liberté dans le nommage, et facilite le découpage hiérarchique des clés, par exemple :

# features part
features.output = true
features.output.verbose = true

features.log = true
features.log.file = mylogfile.log

Valeurs booléennes

La lecture peut se faire directement sur des booléens. Ansi des valeurs telles que true ou yes ou N donneront des variables de valeur 0 ou 1. La liste suivante donne les valeurs possibles :

  • t ou true ou toute combinaison de majuscules/minuscules (par exemple True) donne 1
  • y ou yes ou toute combinaison de majuscules/minuscules (par exemple yEs) donne 1
  • f ou false ou toute combinaison de majuscules/minuscules (par exemple FaLsE) donne 0
  • n ou no ou toute combinaison de majuscules/minuscules (par exemple nO) donne 0
  • 0 et 1 donne bien entendu respectivement 0 et 1

Valeurs multi-lignes

Une valeur peut s’étendre sur plusieurs lignes, sans avoir à modifier le contenu (pas de backslash à rajouter en fin de ligne), à l’instar des HEREDOC de Python, PHP ou autre Perl. Dans ce cas, la clé doit être suivie de la suite {{{, et la valeur doit se terminer par la suite }}} en début de ligne, et éventuellement suivie d’espaces. Par exemple :

ma.liste.de.commandes = {{{
ls
touch toto

# ls encore une fois :-)
ls
rm -f toto
}}}

Dans ce cas, la valeur est reprise telle quelle de la source (les commentaires, lignes vides et espaces faisant partie de la valeur, ne sont pas ignorés, sauf si demandé) - après lecture, la valeur contiendra exactement :

ls
touch toto

# ls encore une fois :-)
ls
rm -f toto

Noter que les valeurs multi-lignes peuvent elles-mêmes contenir des valeurs mutli-lignes (imbriquées), de fait qu’il est possible de lire à partir de la propriété :

valeur = {{{
imbriqué = {{{
toto
}}} # imbriqué
}}} # valeur

le contenu :

imbriqué = {{{
toto
}}} # imbriqué

Les commentaires en fin de valeur sont purement informatifs, et non nécessaires.

Substitutions de variables

Si demandé expréssement, certaines variables d’environnement peuvent être substituées. On peut alors remplacer la valeur d’une variable par son contenu dans l’ensemble de la valeur de la propriété, lors de la lecture ou de l’écriture de celle-ci. Dans le fichier source, les variables peuvent être référencées par un préfixe $ ou encadrées par des @, au choix (le style avec les @ doit être spécifié lors de la lecture ou l’écriture).

Les variables d’environnement doivent être exportées pour être utilisables par la substitution.

Par exemple, nous avons deux variables d’environnement :

export version=5.4
export nom="properties"

Et un fichier de configuration contenant la propriété suivante :

ma.description = {{{
Version = $version
Nom = $nom
}}}

Lors de la lecture, en passant “-u version,nom“, le résultat sera :

Version = 5.4
Nom = properties

On peut également indiquer un préfixe de substitution, afin d’éviter les conflits dans l’environnement :

export soft_version=5.4
export soft_nom="properties"

Le fichier de configuration n’a pas changé, et contient toujours les noms de variables plus simples :

ma.description = {{{
Version = $version
Nom = $nom
}}}

Lors de la lecture, en passant “-u soft_:version,nom“, donc avec la liste des variables préfixée du nom de préfixe plus le signe “:“, le résultat sera :

Version = 5.4
Nom = properties

Avec le style @ cela donne :

ma.description = {{{
Version = @version@
Nom = @nom@
}}}

Avec le paramètre suivant “-u @soft_:version,nom” (notez le @ juste avant le préfixe).

Activation

Vous trouverez le module sur subversion, dans le sous-projet nlib :

nprop.sh
nprop.d/create.awk
nprop.d/delete.awk
nprop.d/find.awk
nprop.d/read.awk
nprop.d/shared.awk
nprop.d/update.awk

Copiez ces fichiers là où bon vous semble (en gardant le répertoire nprop.d au même niveau que le script nprop.sh). Intégrer ce module à des scripts est alors aussi simple que de le sourcer et d’appeler ses fonctions :

source chemin/vers/lib/nprop.sh || {
  echo "problème pour sourcer nprop.sh"
  exit 1
}
 
nprop_read -i my.conf -p my.property -v ma_variable
echo "dans my.conf, 'my.property' vaut: $ma_variable"

Si vous désirez placer le répertoire nprop.d ailleurs, ou le renommer, il suffit de renseigner une variable juste avant d’utiliser les fonctions (avant ou après avoir sourcé nprop.sh) :

# les scripts awk se trouvent dans le répertoire conf_properties
NPROPVAR_INTERNAL_DIR=/chemin/vers/les/libs/conf_properties
source chemin/vers/lib/nprop.sh || exit $?
# appel des fonctions
source chemin/vers/lib/nprop.sh || exit $?
# les scripts awk se trouvent dans le répertoire conf_properties
NPROPVAR_INTERNAL_DIR=/chemin/vers/les/libs/conf_properties
# appel des fonctions

Les fonctions publiques

nprop_read()

Lit la valeur d’une propriété.

synopsys

nprop_read -p property [-i input] [-v var] [-s strip-option] [-u substitutes] [-a noprop|empty]
  • -p nom de la propriété à lire
  • -i indique le fichier à lire (flux d’entrée standard si omis)
  • -v indique la variable dans laquelle placer la valeur (flux de sortie standard si omis)
  • -s options de suppression sur les valeurs multi-lignes (rendu telles quelles normalement) :
    • c supprime les commentaires
    • e supprime les lignes vides ou seulement constituées d’espaces ou tabulations
    • b supprime les commentaires et les lignes vides
  • -u substitution de variable d’environnement (voir le paragraphe Substitutions de variables)
  • -a permet de ne pas émettre d’erreur si la propriété n’existe pas (noprop) ou si la valeur est vide (noprop ou empty)

valeurs de retour

  • 0 - ok
  • 1 - propriété inexistante (sauf si -a noprop)
  • 2 - la propriété multi-ligne n’est pas correctement terminée
  • 3 - la valeur de la propriété est vide (sauf si -a noprop ou -a empty)
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

Deux façons de l’utiliser. Avec ou sans la variable de destination (paramètre -v) :

nprop_read -i fichier.conf -p ma.valeur.a.lire -v la_variable || {
  ret=$?
  echo "erreur de lecture de 'ma.valeur.a.lire' : $ret"
  exit $ret
}
echo "la valeur est : $la_variable"
la_variable=`nprop_read -i fichier.conf -p ma.valeur.a.lire` || {
  ret=$?
  echo "erreur de lecture de 'ma.valeur.a.lire' : $ret"
  exit $ret
}
echo "la valeur est : $la_variable"

Cette deuxième approche est à préférer lorsque la valeur peut contenir des caractères interprétables par le shell, car dans ce cas, il ne seront pas interprété (sauf pour les variables substituées explicitement).

nprop_readbool()

Lit la valeur d’une propriété et la convertit en booléen (valeur 0 ou 1), voir le paragraphe Valeurs booléennes. Le fonctionnement est identique à nprop_read().

valeurs de retour

  • 0 - ok
  • 1 - propriété inexistante (sauf si -a noprop)
  • 2 - la propriété multi-ligne n’est pas correctement terminée
  • 3 - la valeur de la propriété est vide (sauf si -a noprop ou -a empty)
  • 4 - la valeut ne peut être converti un booléen
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

nprop_readbool -i fichier.conf -p ma.valeur.booleenne -v la_variable || {
  ret=$?
  echo "erreur de lecture de ma.valeur.booleenne : $ret"
  exit $ret
}
if [ $la_variable -eq 1 ]; then
  echo "la valeur est vraie"
else
  echo "la valeur est fausse"
fi

nprop_find()

Trouve les propriétés dont le nom correspond au pattern spécifié.

synopsys

nprop_find -p pattern [-i input] [-v var]
  • -p pattern pour lequel trouver les propriétés
  • -i indique le fichier à lire (flux d’entrée standard si omis)
  • -v indique la variable dans laquelle placer la valeur (flux de sortie standard si omis)

valeurs de retour

  • 0 - ok
  • 1 - pas de propriété correspondant au patron
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

Deux façons de l’utiliser, comme nprop_read(), avec ou sans variable de destination. Dans les exemples suivants, nous utiliserons ce fichier.conf :

one = 1
one.two = 2
one.two.three = 3a
one.two.dub.three = 3b
dyb.one.two.three = 3c

Le patron sera utilisé dans une expression régulière, la syntaxe est donc simple, tout en permettant des recherches avancées :

nprop_find -i fichier.conf -p 'one.two'

donnera :

one.two

Notez que la recherche se fait sur toute la longueur du nom de propriété, donc l’équivalent du ^ (début de ligne) et du $ (fin de ligne) des expressions régulières est implicite. Pour rechercher des parties, ajoutez .* aux endroits voulus :

recherche de tout ce qui commence par one.two :

nprop_find -i fichier.conf -p 'one.two.*'
one.two
one.two.three
one.two.dub.three

recherche de tout ce qui contient one.two (notez l’absence du point avant one) :

nprop_find -i fichier.conf -p '.*one.two.*'
one.two
one.two.three
one.two.dub.three
dub.one.two.three

cette fois-ci nécessite le point devant .one.two :

nprop_find -i fichier.conf -p '.*.one.two.*'
dub.one.two.three

recherche de ce qui commence par one. et se termine par .three :

nprop_find -i fichier.conf -p 'one..*.three'
one.two.three
one.two.dub.three

nprop_create()

Crée une nouvelle propriété.

synopsys

nprop_create -p property -v value [-i input] [-o output] [-n nextto] [-b prefix] [-u substitutes]
  • -p nom de la propriété à créer
  • -v la valeur de la propriété
  • -i indique le fichier à lire (flux d’entrée standard si omis)
  • -o fichier de destination (si omis, le même que le fichier d’entrée ou la sortie standard si la lecture se fait sur l’entrée standard)
  • -n placer la nouvelle propriété juste après celle-ci au lieu de la fin de fichier
  • -b ajouter cette chaîne avant la nouvelle propriété (pratique pour les commentaires)
  • -u substitution de variable d’environnement (voir le paragraphe Substitutions de variables)

valeurs de retour

  • 0 - ok
  • 1 - la propriété existe déjà
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

nprop_create -i fichier.conf -p ma.valeur.a.creer -v la_valeur || {
  ret=$?
  echo "erreur de création de 'ma.valeur.a.creer' : $ret"
  exit $ret
}

nprop_update()

Met à jour une propriété existante.

synopsys

nprop_update -p property -v value [-i input] [-o output] [-u substitutes]
  • -p nom de la propriété à modifier
  • -v la nouvelle valeur
  • -i indique le fichier à lire (flux d’entrée standard si omis)
  • -o fichier de destination (si omis, le même que le fichier d’entrée ou la sortie standard si la lecture se fait sur l’entrée standard)
  • -u substitution de variable d’environnement (voir le paragraphe Substitutions de variables)

valeurs de retour

  • 0 - ok
  • 1 - la propriété n’existe pas
  • 2 - la valeur existante multi-ligne ne se termine pas correctement
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

nprop_update -i fichier.conf -p ma.valeur.a.modifier -v la_valeur || {
  ret=$?
  echo "erreur de mise à jour de 'ma.valeur.a.modifier' : $ret"
  exit $ret
}

nprop_write()

Crée ou met à jour une propriété, en appellant nprop_create() puis nprop_update() si la première échoue. Cela permet de placer une valeur dans une propriété sans se soucier du fait qu’elle existe ou pas auparavant. L’appel est identique à nprop_create().

synopsys

nprop_write -p property -v value [-i input] [-o output] [-n nextto] [-b prefix] [-u substitutes]

valeurs de retour

  • 0 - ok
  • 2 - la valeur existante multi-ligne ne se termine pas correctement
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

nprop_write -i fichier.conf -p ma.valeur.a.ecrire -v la_valeur || {
  ret=$?
  echo "erreur de création de 'ma.valeur.a.ecrire' : $ret"
  exit $ret
}

nprop_delete()

Supprime une propriété

synopsys

nprop_delete -p property [-i input] [-o output] [-a noprop]
  • -p nom de la propriété à supprimer
  • -i indique le fichier à lire (flux d’entrée standard si omis)
  • -o fichier de destination (si omis, le même que le fichier d’entrée ou la sortie standard si la lecture se fait sur l’entrée standard)
  • -a noprop permet de ne pas émettre d’erreur si la propriété n’existe pas

valeurs de retour

  • 0 - ok
  • 1 - la propriété n’existe pas (sauf si -a noprop)
  • 2 - la valeur existante multi-ligne ne se termine pas correctement
  • 127 - mauvais appel (vérifier les paramètres)

utilisation

nprop_delete -i fichier.conf -p ma.valeur.a.supprimer || {
  ret=$?
  echo "erreur de suppression de 'ma.valeur.a.supprimer' : $ret"
  exit $ret
}

Un exemple concret: Ngadkm

En fait, il ne s’agit pas de Ngadkm, mais d’un exemple similaire, qui va permettre de découvrir les différentes utilisations de fonctions. De même, certaines valeurs dans la configuration sont purement fictives, et ne servent que pour le propos de ce tutoriel.

Dans les commandes enregistrées dans la config, on retrouve des appels à dkm_* , il s’agit de fonctions internes à Ngadkm, ne vous en souciez pas. Pour info, dkm_exec permet de gérer à la fois si la sortie des commandes doit être affichée ou non à l’écran, selon un paramétrage, et de mettre fin au script si la commande échoue (renvoie une valeur différente de 0).

Imaginons donc que nous faisions un script qui va lire des informations sur un package (ici gcc-pass1) dans un fichier de configuration package.conf, et les afficher à l’écran. Un vrai script pourra construire ledit package, selon les instructions trouvées dans le livre LFS.

Le fichier de configuration

# -----------------------------------------------------------------------------
# gcc-pass1
gcc-pass1.name = gcc
gcc-pass1.version = 4.1.1
gcc-pass1.chroot = true	# it's a fake :-)
gcc-pass1.srctree = @name@-@version@
gcc-pass1.archive.1 = @mirror.gnu@/@name@/@srctree@/@name@-core-@version@.tar.bz2
gcc-pass1.archive.2 = @mirror.gnu@/@name@/@srctree@/@name@-g++-@version@.tar.bz2
gcc-pass1.patch.1 = @mirror.lfs@/@srctree@-fictive.patch
gcc-pass1.commands = {{{

dkm_unpack @archive1@
dkm_unpack @archive2@
dkm_patch @patch1@

dkm_mkdir @name@-build
dkm_cd @name@-build

dkm_exec ../@srctree@/configure \
	--prefix=/tmpsys \
	--with-local-prefix=/tmpsys \
	--disable-nls \
	--enable-shared \
	--enable-languages=c

dkm_exec make bootstrap
dkm_exec make install
dkm_exec ln -vs gcc /tmpsys/bin/cc

dkm_cd
dkm_exec rm -Rf @name@-*

}}}

# vi:set ts=2 sw=2 noet syn=sh:

Remarquez que chaque ligne débute par gcc-pass1, qui est le nom du package. On définit alors plusieurs propriétés à ce package :

  • name le vrai nom du logiciel, si le package a un nom spécial (ce qui est le cas ici, car on a rajouté -pass1)
  • version la version du logiciel
  • chroot le package doit être construit dans un chroot ou non ?
  • srctree le nom du répertoire dans lequel la ou les archives seront décompressées. Si cette propriété n’existe pas, le script déduira automatiquement qu’il s’agit de nom-version
  • archive.* une liste de tarballs à aller chercher. Commence à 1 et doit être une suite simple (1,2,3,..)
  • patch.* une liste de patchs à aller chercher. Même principe que pour les archives
  • commands les commandes à exécuter pour construire le package. On voit ici la syntaxe d’une propriété multi-lignes

Vous avez sûrement remarqué les notations @letruc@. Il s’agit de substitutions de variables. Ces variables correspondent toutes à des propriétés, à quelques exceptions près :

  • @name@gcc-pass1.name
  • @version@gcc-pass1.version
  • @srctree@gcc-pass1.srctree
  • @archive1@ et archive2gcc-pass1.archive.1 et gcc-pass1.archive.2
  • @patch1@gcc-pass1.patch.1
  • @mirror.*@ sont les exceptions. Ces valeurs ne seront pas substituées, mais analysées par une autre partie du programme pour tenter de télécharger les fichiers sur différents mirroirs paramétrés (un exemple sera donné à la fin du tutoriel)

Par exemple, la ligne :

gcc-pass1.srctree = @name@-@version@

Donnera comme résultat “gcc-4.1.1” après les substitutions, et :

gcc-pass1.archive.1 = @mirror.gnu@/@name@/@srctree@/@name@-core-@version@.tar.bz2

Donnera “@mirror.gnu@/gcc/gcc-4.1.1/gcc-core-4.1.1.tar.bz2” (car comme dit précédemment, les variables @mirror.*@ ne sont pas substituées).

Le script de lecture

Créons maintenant un script shell gccinfo.sh, qui va lire toute cette configuration.

Initialisations

La première chose à faire est de sourcer nprop.sh comme expliqué plus haut.

#!/bin/bash
 
source where/prop/is/nprop.sh || {
	ret=$?
	echo Error while sourcing the property module
	exit $ret
}

Ensuite, on va nommer deux variables pour faciliter notre notation :

# le chemin du fichier de configuration
conf=package.conf
 
# le nom du package à construire - notez que cela correspond au début de la ligne de configuration.
# Lorsqu'on voudra en faire un autre, on n'aura qu'à changer cette ligne.
pkg=gcc-pass1

Enfin, on va réinitialiser les variables qui seront lues. Pour cela, on utilise une fonctionnalité des shell Bourne : l’initialisation de la valeur d’une variable peut se faire après son export (exporter ne signifie pas “je délcare que la variable vaut ça pour tout le monde à partir de maintenant”).

unset pkg_name pkg_version pkg_chroot pkg_srctree pkg_archives pkg_patches
export pkg_name pkg_version pkg_chroot pkg_srctree pkg_archives pkg_patches

On va en profiter pour se faire un petit raccourci pour le paramètre de substitution de variables :

substs='@pkg_:name,version,chroot,srctree'

Lectures

On va maintenant lire les valeurs. Etant donné que l’on a exporté toutes les variables dès le début, la substitution fonctionnera sur les différentes variables au fur et à mesure qu’elles seront lues :

# lit le nom réel, et s'il n'est pas défini, prend le nom du package par défaut
nprop_read -i $conf -p $pkg.name -v pkg_name || pkg_name=$pkg
# à ce point précis, la substitution @name@ fonctionne
 
# lit la version. La syntaxe sans nom de variable permet d'initialiser $pkg_version,
# même si la fonction ne retourne rien. On se fout également de savoir si la fonction
# a échoué ou non
pkg_version=`nprop_read -i $conf -p $pkg.version`
 
# on regarde si on a besoin d'entrer dans un chroot pour construire le package
# Le fichier de configuration contient 'true', mais comme on utilise nprop_readbool(),
# la fonction retournera 1.
# Si la fonction échoue, on initialise la variable avec 0
nprop_readbool -i $conf -p $pkg.chroot -v pkg_chroot || pkg_chroot=0
 
# On lit le nom du répertoire créé par l'extraction de l'archive (on utilise ici les
# substitutions
pkg_srctree=`nprop_read -i $conf -p $pkg.srctree -u $substs` || {
	# Si c'est pas bon (la propriété n'existe pas ou la valeur est vide
	# on met par défaut "$pkg_name-$pkg_version" ou juste
	# "$pkg_name" si $pkg_version est vide
	srctree_version=${pkg_version:+'-'$pkg_version}
	pkg_srctree=$pkg_name$srctree_version
}

On va maintenant lire les archives. Puisqu’il peut y en avoir plusieurs, on va rentrer dans une boucle pour lire les propriétés gcc-pass1.archives.* à partir de 1 et séquentiellement, jusqu’à ce que ça plante :

# on commence à 1
num=1
 
# nprop_read() renvoie un nombre différent de 0 en cas d'échec, ce qui rend le test
# de while faux
while nprop_read -i $conf -p $pkg.archive.$num -v archive -u $substs; do
 
	# on veut générer des variables pkg_archive<num> pour les substituer aux
	# @archive<num>@. Comme on ne sait pas à l'avance le nom de la variable,
	# on utilise eval qui va transformer notre chaîne en instructions du shell
	eval "
# crée la variable pkg_archive<num> avec le nom du fichier archive (sans le chemin)
pkg_archive$num=`basename $archive`
 
# on exporte, sinon ce ne sera pas substitué
export pkg_archive$num
 
# la variable $archsubst sera utilisé en complément de $substs, pour y ajouter nos
# @archive<num>@
archsubst=$archsubst,archive$num
"
 
	# on ajoute la nouvelle archive à la liste (séparé par un espace) $pkg_archives
	if [ -n "$pkg_archives" ]; then
		# si pas vide, liste="<ancienne liste><espace><nouvelle archive>"
		pkg_archives="$pkg_archives $archive"
	else
		# sinon, liste=<archive>
		pkg_archives=$archive
	fi
 
	# test suivant
	num=`expr $num + 1`
done

On fait pareil pour les patchs :

num=1
while nprop_read -i $conf -p $pkg.patch.$num -v patch -u $substs; do
	eval "pkg_patch$num=`basename $patch`;export pkg_patch$num;patchsubst=$patchsubst,patch$num"
	if [ -n "$pkg_patches" ]; then
		pkg_patches="$pkg_patches $patch"
	else
		pkg_patches=$patch
	fi
	num=`expr $num + 1`
done

Notez comme le code est concis quand on se passe de commentaires :-)

Enfin, on lit les commandes, en faisant toutes les substitutions possibles :

pkg_commands=`nprop_read -i $conf -p $pkg.commands -u $substs$archsubst$patchsubst`

Affichage

On va afficher les infos que l’on a eu :

echo package: $pkg
echo '	name:' $pkg_name
echo '	version:' $pkg_version
echo '	chroot?:' $pkg_chroot
echo '	srctree:' $pkg_srctree
echo '	archives:' $pkg_archives
echo '	patches:' $pkg_patches
echo "	commands:
>>>$pkg_commands<<<
"

Résultat à l'écran

package: gcc-pass1
        name: gcc
        version: 4.1.1
        chroot?: 1
        srctree: gcc-4.1.1
        archives: @mirror.gnu@/gcc/gcc-4.1.1/gcc-core-4.1.1.tar.bz2 @mirror.gnu@/gcc/gcc-4.1.1/gcc-g++-4.1.1.tar.bz2
        patches: @mirror.lfs@/gcc-4.1.1-fictive.patch
        commands:
>>>
dkm_unpack gcc-core-4.1.1.tar.bz2
dkm_unpack gcc-g++-4.1.1.tar.bz2
dkm_patch gcc-4.1.1-fictive.patch

dkm_mkdir gcc-build
dkm_cd gcc-build

dkm_exec ../gcc-4.1.1/configure \
        --prefix=/tmpsys \
        --with-local-prefix=/tmpsys \
        --disable-nls \
        --enable-shared \
        --enable-languages=c

dkm_exec make bootstrap
dkm_exec make install
dkm_exec ln -vs gcc /tmpsys/bin/cc

dkm_cd
dkm_exec rm -Rf gcc-*<<<

Conclusion

Ce tutoriel est terminé. Nous n’avons vu que les fonctions de lecture, mais l’écriture est similaire (aka simple :-) ). Peut-être qu’un jour de continuerai avec ces dernières (en fonction des demandes).

vous trouverez les sources complets ici. Pour tout commentaire, critique, demandes, contactez-moi.

riri

 
nlib_nprop.txt · Dernière modification: 07/10/2007 12:39 par riri
 
Recent changes RSS feed Creative Commons License Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki