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.
# 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
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 :
True) donne 1yEs) donne 1FaLsE) donne 0nO) donne 0Une 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.
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).
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
Lit la valeur d’une propriété.
nprop_read -p property [-i input] [-v var] [-s strip-option] [-u substitutes] [-a noprop|empty]
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).
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().
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
Trouve les propriétés dont le nom correspond au pattern spécifié.
nprop_find -p pattern [-i input] [-v var]
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
Crée une nouvelle propriété.
nprop_create -p property -v value [-i input] [-o output] [-n nextto] [-b prefix] [-u substitutes]
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 }
Met à jour une propriété existante.
nprop_update -p property -v value [-i input] [-o output] [-u substitutes]
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 }
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().
nprop_write -p property -v value [-i input] [-o output] [-n nextto] [-b prefix] [-u substitutes]
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 }
Supprime une propriété
nprop_delete -p property [-i input] [-o output] [-a noprop]
nprop_delete -i fichier.conf -p ma.valeur.a.supprimer || { ret=$? echo "erreur de suppression de 'ma.valeur.a.supprimer' : $ret" exit $ret }
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.
# -----------------------------------------------------------------------------
# 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 :
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 :
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).
Créons maintenant un script shell gccinfo.sh, qui va lire toute cette configuration.
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'
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`
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<<< "
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-*<<<
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