Bash scripting Course

Lesson 1: The Foundation

  1. What you need:
    • an ascii editor. Vim on linux, emacs, whatever.
    • a linux/unix machine with bash installed.
  2. Vocabulary: in programming, you need to understand the 5 following words:
    • constant: things that don't change
    • variable: things that do change
    • type: sort of
    • procedure: piece of code
    • function: piece of code returning a result
    • scope: visibility

Many languages don't have a procedure anymore - you just use a function that doesn't return anything.
Similarly constants are just variable that don't change (ideally). (*)
JS is amongst those: it doesn't know procedure or constants. It is still usefull to use them.
It is a good idea to mark the type of a variable in the name.
For example: screenB is of type boolean, screenS is a string, screenI is an integer.
Writing convention are useful to ensure you spell things consistently. Since unix is case-sensitive, it is more than important.


(*) Hence the famous programmer joke: constants aren't - variables won't.

[Top]

Lesson 2: The basics

I have taken as first example a simple script that I wrote to automatically download TV programs.

#! /bin/bash
XMLTVDir="/home/francois/.xmltv"
echo "Generation master XML for TV programs"
today=`date +"%Y%m%d"`
cd $XMLTVDir
mkdir $today
cd $today
echo "Generating UK senders"
tv_grab_uk_bleb > uk.xml
echo "Generating French senders (this can take up to 4 hours!)"
tv_grab_fr > fr.xml
echo "Done - You can now use GenProg for fresh html listings"
cp uk.xml ../latest
cp fr.xml ../latest

Examinons cela ligne par ligne.
La première ligne est la plus importante:

#! /bin/bash
C'est elle qui va déterminer l'interpréteur de commande que le système va devoir charger pour comprendre la suite des instructions.
Le shell le plus utilisé sous linux est bash, mais ce n'est pas une évidence ! Chez les vieux programmeur unix, le shell par défaut est en général tcshell, et sur Solaris, c'est korn. C'est important de le savoir car la syntaxe n'est pas la même (voir: BASIC)
XMLTVDir="/home/francois/.xmltv"
ça, c'est une variable. On assigne à la variable XMLTVDir la valeur "/home/francois/.xmltv". C'est une chaîne de caractère, donc elle est entre guillemets.
C'est une variable, mais en fait elle va nous servir de constante, car sa valeur ne changera pas pendant toute la durée du programme. Je travaille souvent avec des constante et des variable car c'est plus facile par la suite de modifier le programme si je veux le changer de machine ou simplement si je ré-organise mes répertoires.

Par définition, quand il ne se passe rien, l'utilisateur devient nerveux, moi compris. Donc je parsème mes script de retour à l'écran. En plus cela facilite le débogage car on voit tout de suite si le programme est arrivé à une ligne ou pas. Donc on envoie de l'info à l'écran:

echo "Generation master XML for TV programs"
(aka: à quoi sert ce truc)

today=`date +"%Y%m%d"`
Une autre assignation de variable, mais beaucoup plus intéressante. Décomposons les choses:
today=``
C'est ce qui donne à unix toute sa puissance. La valeur que l'on va donner à la variable est le résultat d'une commande système, que l'on appelle en la mettant entre simple guillemets (pas le double de la déclaration de chaîne), tournés vers la droite. La plupart des gens l'ignore, mais nous avons sur le claviers 3 guillemets simples:
  1. Le vertical: ' qui est le plus utilisé, c'est en général l'apostrophe. Sur l'azerty il est sur la première rangée, en dessous du 4.
  2. Celui tourné vers la gauche:´, troisième ligne du clavier, avec le ù et le %, accès par AltGr
  3. Celui tourné vers la gauche:`, troisième ligne du clavier, avec le µ et le £, accès par AltGr
Cela permet donc de récupérer dans une variable (ou autrement) le résultat de n'importe laquelle des 94 commande de base Unix ou en fait de n'importe quel programme envoyant son output vers stdout. (stderr aussi mais c'est plus compliqué) La suite, c'est une simple utilisation de la fonction unix standard date.
Date renvoie par défaut un truc comme cela: Tue Feb 27 15:35:08 CDT 2015
Mais on peut passer des paramètre pour avoir le résultat que l'on veut. Voir à cet égard le manuel unix, c'est un cours de bash scripting, pas de unix :-)
Bref. Ce dont j'ai besoin, c'est la date sous la forme aaaammjj, donc: 20150215. Le paramètre pour cela est %Y%m%d.
Donc date +"%Y%m%d
today=`date +"%Y%m%d"` 
va donc attribuer à la variable today la valeur 20150211

cd $XMLTVDir 
: Comme je le disais, on peut utiliser dans un script n'importe quelle commande unix, ici en l'occurrence cd qui permet de changer de répertoire. On lui passe comme paramètre le répertoire où on veut aller, en l'occurrence ce qui est contenu ($) dans la variable XMLTVDir. Il est à noter que le script ne vérifie pas si le répertoire existe.

Une fois dans ce répertoire, on crée une sous répertoire qui va avoir pour nom la date du jour, contenue dans ($) today:

mkdir $today
Ici non plus, on ne vérifie pas qu'il n'y a pas d'erreur (pas bien !)
On va dans le répertoire en question et on affiche un truc :
cd $today
echo "Generating UK senders"
Puis on appelle un programme installé qui s'appelle tv_grab_uk_bleb et on redirige l'output ver un fichier uk.xml situé dans le répertoire courant.
tv_grab_uk_bleb > uk.xml

Une fois que c'est fait, on prévient que la suite va prendre du temps et on fait la même chose pour les émetteurs français:

echo "Generating French senders (this can take up to 4 hours!)"
tv_grab_fr > fr.xml

Enfin on informe l'utilisateur que c'est fini et qu'il peut passer à la suite (on pourrait lancer le programme suivant depuis le script, mais la procédure est volontairement coupée en morceaux):

echo "Done - You can now use GenProg for fresh html listings"

et on copie les fichiers généré dans un autre répertoire, qui contient toujours la dernière version.
cp uk.xml ../latest
cp fr.xml ../latest
Cela permet de garder un historique au cas où les procédures suivantes détruiraient les fichiers – on ne doit pas repasser 4h à les descendre.
De plus, les scripts suivants pointeront toujours sur "latest", sans se préoccuper de la date. Mis à part le truc de la date, jusqu'à présent, c'est pas bien compliqué ? :-)


[Top]

Lesson 3: Really Starting


On continue avec le deuxième script, celui qui va transformer les XML obtenus sur le net en quelque chose d'intéressant. La plus grosse partie de cette transformation est faite par une humm heu – transformation XSL, mais le script a quelques éléments intéressants.
#!/bin/bash
# where are the listing ?
XMLTVDir="/home/francois/.xmltv/"
# some functions we'll need
Sort() {
    echo "Sorting $1.xml"
   sax $1.xml $XMLTVDir/util/cl.xsl > $1s.xml
}
Genere() {
    echo "Generating $1.htm"
   sax $1s.xml $XMLTVDir/util/prgs.xsl > $1.htm
}

#Rock'n roll
echo "Generation HTML for TV programs"
cd $XMLTVDir/latest

if [[ $# -eq 0 ]] ; then
# to move to external config
    Sort uk
    Sort fr
   # Genere uk
   # Genere fr
    exit 0
else
  for var in "$@"
   do
    Sort $var
    # Genere $var
  done
fi

# have a look
#xombrero uk.htm fr.htm &
Reprenons ligne par ligne.
#!/bin/bash
# where are the listing ?
XMLTVDir="/home/francois/.xmltv/"
Same procedure as last year: on déclare notre interpréteur et une variable (qui sera une constante) – rien de bien neuf ici.
# some functions we'll need

Sort() {
    echo "Sorting $1.xml"
   sax $1.xml $XMLTVDir/util/cl.xsl > $1s.xml
}

Genere() {
    echo "Generating $1.htm"
   sax $1s.xml $XMLTVDir/util/prgs.xsl > $1.htm
}
Ici c'est beaucoup plus rigolo et plus utile. Comme subtilement indiqué en commentaire, je déclare/crée des fonctions. Des fonctions c'est quoi ? tout simplement des bouts de code que je risque d'être amené à utiliser plusieures fois. Alors, au lieu de le ré-écrire, je l'isole dans une fonction spéciale que je n'écris qu'une fois et que j'appellerai chaque fois que j'en aurai besoin. Pour faire une fonction, on donne un nom, on met des parenthèses, et on met le code entre accolades. Simple.
MaFonction() {

#des trucs ici – mon code

}
En l'occurrence j'ai deux fonctions, Sort et Genere qui appellent saxon (le processeur XSL) en lui passant les bon paramètres. Pour être utile, une fonction doit pouvoir être utilisée dans des circonstances différentes, des circonstances variables. Je vais donc passer en paramètre à ma fonction le nom du fichier à traiter. Ce sera le premier paramètre: $1 Il est à noter que le script ne fait encore rien. La description des fonctions n'entraîne pas leur exécutions tant qu'elle ne sont pas appellées dans le code.
#Rock'n roll
Le vrai programme commence ici.
echo "Generation HTML for TV programs"
cd $XMLTVDir/latest
Comme d'habitude, un message pour signaler ce qui se passe, et on va se positionner au bon endroit.
if [[ $# -eq 0 ]] ; then
# to move to external config
    Sort uk
    Sort fr
   # Genere uk
   # Genere fr
    exit 0
else
  for var in "$@"
   do
    Sort $var
    # Genere $var
  done
fi
Beaucoup de choses dans ce petit bout de code, 7 pour être précis. Regardons pas à pas.
if [[  ]] ; then

  else

fi (le fi marque la fin de notre boucle de condition)
tout d'abords une des boucles les plus utilisée en programmation: si / alors / sinon. (1) Cela permet de tester une condition et de réagir en conséquence. La condition se trouve entre double crochets ([[ ici ]]). La condition c'est quoi ? si le nombre de paramètres du script ($#) (2) est null (equal zéro –eq 0). (3) Donc:
if [[ $# -eq 0 ]] ; then
Si le script est appellé sans paramètre
    Sort uk
    Sort fr
On appelle la procédure de tri avec uk et fr en paramètre, c-a-d On trie uk.xml et on trie fr.xml en envoyant le résultat dans uks.xml et frs.xml respectivement
   # Genere uk
   # Genere fr
Puis on appelle la procédure de génération avec lesdits paramètres (en fait en commentaire dontc plus maintenant mais bon)
    exit 0
et puis on sort en disant que tout va bien (on sort avec un code d'erreur=0) (4)
else 
sinon (c-a-d si on a des paramètres) on entame l'autre boucle la plus utilisée, pour autant que
  for var in "$@"
pour chaque (autant que de) variable (var) (5) se trouvant dans la liste des paramètre passée ($@) (6)
   do
faire (début de ma boucle) (7)
    Sort $var
Appeler la procédure en lui passant comme paramètre le contenu de la variable en question ($var).
    # Genere $var
idem
  Done
fini (fin de ma boucle)
fi
(fin de l'autre branche de ma condition)
# have a look
#xombrero uk.htm fr.htm &
(Lancer un browser pour visualiser les pages qui résultent du script.) Voilà

Pas mal de concepts pour un bête petit script de 30 lignes. La bonne nouvelle c'est que pratiquement tout est là.

[Top]