samedi 2 juillet 2011

Vous avez dit sourcer?

Pour mener à bien une compilation, nous avons besoin de définir toute une série de variables d'environnement. Les définitions de ces variables se trouvent par exemple dans un fichier nommé def-var:

export QTDIR=`kde4-config --qt-prefix`
export KDEDIR=`kde4-config --prefix`
export KDEDIRS=$KDEDIR
export KDE_BUILD=$KDEDIR
export DBUSDIR=$KDEDIR
export LD_LIBRARY_PATH=$QTDIR/lib:$KDEDIR/lib:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=$DBUSDIR/lib/pkgconfig:/usr/lib/pkgconfig
export PATH=$QTDIR/bin:$KDEDIR/bin:$PATH

Si nous exécutons def-var dans un terminal et que nous regardons ensuite ce qu'il en est au niveau des variables d'environnement existant dans le terminal, rien n'est changé.
Alors comment procéder? Taper toutes ces commandes?
Non, bien sûr: il existe une autre méthode.

Bon, reprenons en partant d'un fichier un peu plus ludique: le fichier bjr:

echo $PPID-$$
echo Saisis un nom $moi
read moi
cat << FIN
Toujours saluer le directeur:
Bonjour $moi
FIN

situé dans /home/toto/test.
Ce fichier contient toute une série de commandes.
Elles sont là, déjà écrites dans le fichier! Alors pourquoi les retaper?
Il suffit d'indiquer à bash que bjr constitue une source de commandes:


L'output de la commande echo est affiché en premier. Puis la commande "read moi" provoque l'affichage du curseur: le système est en attente de ce que nous allons taper au clavier afin de le mettre dans la variable moi:


Terminons la saisie par un appui sur ENTER:


Tapons effectivement à l'écran la première commande de bjr:


Le résultat est le même:  2849 est le pid du shell bash dans lequel nous sommes et 2838 celui de son processus parent (par exemple konsole ou gnome-terminal)
Lorsque bjr est sourcé, tout se passe comme si les commandes qu'il contient étaient tapées à l'écran. Ainsi la variable moi est maintenant définie dans le shell bash (pid 2849) avec la valeur "Ahuri".

Le mot "source" étant très long (et donc fatiguant à taper), il est le plus souvent remplacé par un point ("."):


Nous constatons que la variable moi est bel et bien définie (elle contient le mot "Ahuri") avant même l'exécution de la commande read

Et si nous exécutions bjr en tapant tous simplement son nom, comme pour n'importe quelle commande?
Problème:

C'est normal, bjr n'est pas trouvé car /home/toto/test ne figure dans la variable d'environnement PATH qui contient l'ensemble des répertoires où le système recherche les commandes.
Par curiosité, examinons le contenu de PATH:


/home/toto/test n'y figure évidemment pas, mais bien à toutes fins utiles /home/toto/bin.
Ainsi nous pourrions créer le lien symbolique qui convient.
ln -s ../test/bjr ../bin
Mais la réponse classique au problème consiste à indiquer au système où se trouve exactement la commande à exécuter en tapant:
/home/toto/test/bjr
ou plus court:
./bjr

En fait c'est la même chose:


Nous affichons d'abord les inodes des différents fichiers du dossier (y compris les fichiers cachés). En faisant une recherche sur l'inode 1717900 nous voyons que le fichier "." n'est autre que le dossier dans lequel on se trouve (/home/toto/test).
Ceci est bien connu et a déjà été expliqué dans un autre billet.

Le point (.) peut donc être mangé à plusieurs sauces.

Résumons:
. bjr : le point remplace la commande "source". bjr sera sourcé.
./bjr : le point remplace (ici) /home/toto/test. bjr sera exécuté.

Procédons à l'exécution (!):


Nouveau problème! Et oui: personne n'a le droit d'exécuter ce fichier, même pas le root.
Avant de procéder il faut d'abord donner le droit d'exécution sur bjr (avec la commande chmod +x bjr):


Intéressons nous à ce que vaut maintenant la variable moi dans le shell bash de base:


C'est encore la valeur résultant du "read" lorsque bjr a été sourcé pour la deuxième fois.

Constatations:
  • Les différentes commandes du script bjr, s'exécutent au sein d'un sous-shell (pid 2946) dérivé du shell bash principal (pid 2849).
  • Dans ce sous-shell, la variable moi n'est au départ pas définie (elle prend ensuite la valeur Jules).
  • L'exécution de bjr ne modifie pas la valeur de la variable moi dans le shell bash principal (contrairement à ce qui se passe lorsque bjr est sourcé).
    La variable moi n'est pas définie au départ dans le sous-shell où bjr tourne car ce n'est pas une variable d'environnement. Et quand bien même l'aurait-elle été (un petit export moi tapé à l'écran et hop!), cela aurait changé quoi ?
    • Elle aurait alors été définie d'emblée dans le sous-shell 2946 et la ligne 2 du résultat de l'exécution de bjr aurait été:  Saisis un nom Patate
    • Sa valeur serait comme avant restée inchangée au niveau shell maître
      Un changement de valeur dans un sous-shell ne se répercute pas dans le shell parent.

      Afin de consolider toutes ces notions, raisonnons maintenant sur un cas un peu plus compliqué, metttant en scène le fichier lbjr:

      echo $PPID-$$
      echo Bonjour $moi
      . defvar
      ./bjr
      echo Il répondra: appelez-moi $moi

      et le fichier defvar

      echo $PPID-$$
      moi=Big\ Boss

      (Remarquons le caractère d'échappement "\" qui enlève au blanc qui suit le mot "Big" son rôle de séparateur)

      Exécutons lbjr. Mais comme nous n'avons pas le courage de le rendre exécutable, procédons en utilisant la commande bash:


      Analysons le résultat:
      • ligne 1: lbjr s'exécute dans un sous-shell (pid 2949)
      • ligne 2: dans ce sous-shell la variable moi n'est pas définie
      • ligne 3: defvar est sourcé au niveau du sous-shell et la commande echo de defvar s'exécute bien dans ce sous-shell
      • ligne 4: bjr s'exécute dans un sous-sous-shell (pid 2950)
      • dernière ligne: bjr est terminé, on est retombé dans le sous-shell 2949 et la variable moi est est maintenant définie dans ce sous-shell car defvar y a été sourcé.
      Si defvar n'avait pas été sourcé, mais exécuté (commande ./defvar au lieu de . defvar), la variable moi serait restée non définie dans le shell pid 2949.

      Nous n'avons pas eu le courage de rendre lbjr exécutable, mais bash c'est quand même très long à écrire: pourquoi ne pas utiliser sh?
      Après tout sh est maintenant le plus souvent un simple lien vers bash:


      Et pourtant:


      Et oui : le comportement de bash change suivant la façon dont il est invoqué.
      Le mieux est d'indiquer précisément l'endroit où se trouve le fichier à sourcer, ce qui évite ce genre d'erreur.

      Pour en revenir avec le problème qui a motivé ce billet, la solution est bien évidemment de sourcer def-var dans le shell bash de travail. Les variables d'environnement, nouvelles où modifiées, seront ainsi présentes pour tous les processus fils