4. La programmation

Voilà maintenant la substance même du scripting : les commandes, qui ont jusque là été saisies interactivement, vont être regroupées dans un fichier texte et destinées à être exécutées (ou pas !), séquentiellement (ou pas !) par un shell afin d'obtenir un résultat donné (et de préférence toujours le même !) pour une valeur initiale donnée.

4.1. Les variables, interprétation, substitution et protection des commandes

Au sein d'un script, les variables du shell telles qu'elles ont été décrites infra permettront de mémoriser des données en cours de traitement ou des états du programme durant son exécution : savoir les manipuler, et comprendre comment le shell les interprète ou les substitue est donc primordial.

Au lancement d'un script, le shell chargé de son exécution va commencer à exécuter séquentiellement chacune des lignes le composant, et après une vérification (lexicale et syntaxique) des éventuelles erreurs qu'il pourrait contenir, procéder à son évaluation (phase d'interprétation et de substitution des variables et des commandes), pour enfin l'exécuter et poursuivre le parcours du fichier.

Note

Contrairement à d'autres langages interprétés, et à fortiori aux langages compilés, le shell ne connait pas d'étape de parsing global du code source à exécuter : les erreurs éventuelles ne seront donc détectées qu'au cas par cas, au fur et à mesure de l'exécution.

4.1.1. Nommage, déclaration et suppression de la définition des variables

Pour être valide, le nom de toute variable doit obligatoirement débuter par un des caractères compris dans [a-zA-Z_] : une lettre (majuscule ou minuscule) ou un tiret bas. Les caractères suivants pouvant être compris dans l'ensemble [a-zA-Z0-9_] : c'est à dire les mêmes plus les chiffres.

Exemples de noms valides :

_UnNombre
Un_Nombre
Un_Nombre_

Exemples de noms invalides :

1_Nombre
-Un_Nombre
/Un_Nombre_

La déclaration d'une variable est optionnelle (elle est alors implicitement déclarée lors de son affectation), mais peut faire l'objet d'une instruction particulière via les commandes internes declare ou typeset qui suivent la syntaxe suivante :

declare | typeset [option] [nom[=affectation]]

Utilisées seules, les commandes declare ou typeset se comportent comme la commande set, et affichent l'ensemble des variables déclarées dans l'environnement courant. Les options permettent notamment de spécifier un type à la variable faisant l'objet de la déclaration : -a indique un tableau, -i un nombre entier, l'option particulière -r marque la variable "en lecture seule" : elle ne peut alors être modifiée, ni supprimée ultérieurement (il s'agira donc d'une constante !).

Note

Par défaut une variable du shell représente une chaine de caractères.

Exemples de déclarations de variables :

_Unechaine=texte déclare la variable _UneChaine et lui affecte la valeur 'texte'.
_Unechaine2="texte comportant des espaces" lui affecte la valeur 'texte comportant des espaces'.
typeset _Unechaine déclare la variable _UneChaine et lui affecte la valeur "chaine vide".
declare -i Un_Nombre=25 déclare la variable Un_Nombre en tant que valeur numérique et lui affecte la valeur 25.

La suppression d'une déclaration de variable s'effectue par appel de la commande interne unset [nom].

Exemples de suppressions de variables :

_Unechaine=texte; unset _Unechaine déclare la variable _UneChaine et la supprime aussitôt.
declare -i -r Un_Nombre=25; unset Un_Nombre la tentative de suppression déclenche une erreur...

4.1.2. Les variables spéciales du shell

Au cours de son exécution, un shell positionne un certain nombre de variables qui fournissent des informations de différentes natures. La liste exhaustive de ces variables est fournie ci-après :

4.1.2.1. Les paramètres positionnels

  • $# contient le nombre d'arguments reçus par le script en cours d'exécution.

  • $0 représente le nom du script.

  • $1, $2, $3 ... $9 les 9 premiers arguments passés au script.

  • ${10}, ${11}, ${12} ... les arguments suivants passés au script (uniquement pour bash et ksh).

  • $* et $@ contiennent la liste de l'ensemble des arguments passée au script. La différence entre les deux formes si situe au niveau de leur utilisation avec des guillemets : "$*" ne protège pas les éventuels arguments contenant des séparateurs alors que $@ le fait !

La commande interne shift [n] permet de décaler (de n positions si n est fourni) vers la gauche l'ensemble des arguments. Elle met à jour l'ensemble des paramètres positionnels à l'exception de $0 qui représentera toujours le nom du script. Cette commande peut être utile pour traiter successivement l'ensemble des arguments fournis à un script.

4.1.2.2. Les variables d'état

  • $? contient le code de retour d'une commande. Ce code est un entier compris entre 0 et 255, et permet de diagnostiquer la réussite (0) ou l'échec d'une commande unix (>0).

  • $$ représente le PID du shell en cours d'exécution.

  • $! représente le PID du dernier processus lancé en arrière plan.

La commande interne exit [n] permet d'arrêter l'exécution et de transmettre le code de retour d'un script. Appelée sans argument, exit arrête l'exécution du script et retourne 0. A défaut d'appel de exit, le script se termine en retournant 0.

4.1.2.3. La variable IFS

Cette variable particulière contient en fait les caractères utilisés par le shell comme séparateur (par défaut espace, tabulation et retour à la ligne). Elle peut être modifiée en cours de script.

Exemples d'utilisation :

OLDIFS="$IFS"
IFS=$'\n'
...
IFS="$OLDIFS"

Par ces quelques lignes, le fonctionnemment du shell est modifié pour ne considérer que le saut à la ligne comme séparateur, afin de permettre de traiter des lignes entières comportant des espaces ou des tabulation.

4.1.3. Interprétation et substitution des variables

La syntaxe du shell prévoit donc l'utilisation de certains caractères spéciaux qui subiront un traitement particulier lors de l'interprétation d'une ligne de commande ... et c'est la principale raison de la restriction des possibilités de nommage des variables à un jeu particulier de caractères.

Ces caractères spéciaux sont les suivants :

  • espace tabulation saut de ligne sont les séparateurs de mots dans la ligne de commande (cf. IFS).

  • ; est le séparateur de commandes.

  • '' "" \ sont les caractères de protection.

  • & signifie l'exécution en arrière plan.

  • | < << > >> signifient les tubes et les redirections.

  • () {} signifient les regroupement de commandes.

  • * ? [ ] ?() +() *() !() @() sont les caractères de génération de noms de fichiers.

  • $ ${} permettent d'accéder à la valeur d'une variable.

  • `` $() signifient la substitution de commandes.

4.1.3.1. Utilisation des caractères de protection :

Les ' apostrophes ou simples quotes permettent de retirer la signification de tous les caractères spéciaux du shell ; elles doivent être appariées dans une même ligne de commande, et ne se protègent pas elles-mêmes.

Exemples d'utilisation :

echo $PATH affiche la valeur de la variable $PATH.
echo '$PATH' affiche la chaine '$PATH'.
echo 'erreur d'utilisation' affiche > ... commande non terminée.
echo 'erreur d'\''utilisation' affiche erreur d'utilisation (par appariement des ').

Le caractère \ anti-slash permet de retirer la signification du caractère spécial du shell le suivant immédiatement. Pour afficher le caractère \, il faut le doubler !

Exemples d'utilisation :

echo * affiche les fichiers présents dans le répertoire courant.
echo \* affiche le caractère *.
echo 'erreur d'\''utilisation' affiche erreur d'utilisation (traitement du ' non apparié).

Les " guillemets ou doubles quotes permettent de retirer la signification de tous les caractères spéciaux du shell sauf $ et ${}, `` et $() et enfin \ ainsi qu'elle-même; à l'instar de ' elles doivent être appariées dans une même ligne de commande, et ne se protègent pas elles-mêmes.

Exemples d'utilisation :

echo "Le contenu de la variable PATH=$PATH" affiche le contenu de la variable $PATH.
echo "Le contenu de la variable PATH=\"$PATH\"" affiche la chaine '$PATH'.
echo "ls -l * 1>liste 2&>1" affiche > affiche ls -l * 1>liste 2&>1
echo "$(ls -l * 1>liste 2&>1)"' n'affiche rien mais crée le fichier liste.

4.1.3.2. Isolation d'un nom de variable :

Il est des cas particulies où il ne sera pas possible, même en utilisation les possibilités d'échappement, d'obtenir le résultat escompté : il faut alors pouvoir isoler le nom de la variable utilisée (c'est à dire forcer le shell à n'utiliser qu'une partie de la commande pour en extraire une valeur de variable). C'est notamment le cas lors de la concaténation du contenu d'une variable à une chaine de caractères débutant par un des caractères appartenant à l'ensemble du jeu de caractères valides pour le nommage d'une variable.

Cette isolation s'obtient en utilisant la syntaxe ${VARIABLE}. Exemple :

On cherche à créer des fichiers nommés FIC001_LISTE.txt à partir d'une variable NUM contenant des valeurs telles que '001', '002'... la syntaxe

touch FIC$NUM_LISTE.txt aboutira à créer un fichier FIC.txt, dans la mesure où le shell considère une variable NUM_LISTE qui n'est pas définie.

La solution passe donc par isoler le nom de la variable NOM : touch FIC${NUM}_LISTE.txt .

4.1.3.3. Substitution de variables :

Sous le terme de substitution de variables, se cache simplement la possibilité d'attribuer une valeur par défaut aux variables (qu'elles soient non initialisées).

  • ${variable:-valeur} si la variable n'est pas vide, la substitution retourne sa valeur, sinon elle retourne valeur.

  • ${variable:=valeur} si la variable n'est pas vide, la susbtitution retourne sa valeur, sinon elle retourne valeur et variable est également affectée à valeur.

  • ${variable:+valeur} si la variable est pas vide, la substitution retourne valeur, sinon elle retourne sa valeur, c'est à dire vide.

  • ${variable:?message} si la variable n'est pas vide, la substitution retourne sa valeur, sinon le shell affiche le nom de la variable suivi de la chaine de caractères message. De plus, si cette forme de substitution est utilisée dans un script shell, celui s'arrête immédiatement après l'affichage du message.

Exemples d'utilisation :

VAR=BONJOUR ; echo ${VAR:-'la variable VAR est vide !'}
affiche "BONJOUR".
unset VAR ; echo ${VAR:-'la variable VAR est vide !'}
affiche "la variable VAR est vide !".
VAR=BONJOUR ; echo ${VAR:='la variable VAR est pas vide !'}; echo $VAR
affiche 2 fois "BONJOUR".
unset VAR ; echo ${VAR:='la variable VAR est vide !'}; echo $VAR
affiche 2 fois "la variable VAR est vide !".
unset VAR ; echo ${VAR:+'la variable VAR est vide !'}
n'affiche ... rien.
VAR=BONJOUR ; echo ${VAR:+'la variable VAR est pas vide !'}
affiche "la variable VAR est pas vide !".
unset VAR ; echo ${VAR:?'la variable VAR est vide !'}; echo "suite ..."
affiche "la variable VAR est vide !".
VAR=BONJOUR ; echo ${VAR:?'la variable VAR est vide !'}; echo $VAR
affiche 2 fois "BONJOUR".

4.1.4. Substitution de commandes

La substitution de commandes permet l'utilisation du résultat de l'exécution d'une commande dans une autre commande, ou de l'affecter à une variable. La syntaxe peut indifféremment prendre 2 formes d'écriture :

$(COMMANDE) ou bien `COMMANDE`

Exemples d'utilisation :

echo "liste des fichiers du répertoire : $(ls)"
nbre=$(ls -l | wc -l); nbre=`expr $nbre-1`; echo "nbre de fichiers : $nbre"

4.1.5. Interprétation d'une ligne de commande

L'ensemble des mécanismes vus précédemment peuvent être mis en jeu lors de l'exécution d'une ligne de commande. Ils sont toutefois exécutés dans un ordre précis :

  1. Isolation des mots séparés par caractères séparateurs (par défaut : espace, tabulation, saut de ligne).

  2. Traitement des caractères de protection ('' "" \).

  3. Substitution des variables ($ ${}).

  4. Substitution des commandes (`` $()).

  5. Substitution des caractères de génération des noms de fichiers ('* ? [] ?() +() *() !() @()).

  6. Traitement des tubes et des redirections.

  7. Lancement de la commande.

4.2. Les opérateurs et les structures de contrôle

Le flux d'exécution peut (et doit) être contrôlé en fonction des résultats sucessifs des commandes exécutées ou de l'état de variables d'exécution. Les opérateurs du shell permettent de contrôler simplement le lancement ou non d'une commande en fonction du résultat d'une précédente commande ; alors que les structures de contrôle permettent d'agir plus finement sur le flux d'exécution s'un script.

4.2.1. Les opérateurs du shell

Les opérateurs lient entre elles différentes commandes, et sont au nombre de deux :

  • && signifie ET logique, et exécute la commande suivante si la précédente a retourné VRAI (code retour 0).

  • || signifie OU logique, et exécute la commande suivante si la précédente a retourné FAUX (code retour >0).

La syntaxe d'utilisation est la suivante :

  • CMD1 && CMD2 : CMD2 n'est exécutée que si CMD1 a retourné VRAI, l'ensemble ne retourne VRAI que si toutes les commandes ont retourné VRAI.

  • CMD1 || CMD2 : CMD2 n'est exécutée que si CMD1 a retourné FAUX, l'ensemble retourne VRAI si au moins une des commandes a retourné VRAI.

Exemple d'utilisation :

make bzImage && make modules && make modules_install

Cette séquence (typique de la compilation du noyau Linux) va enchainer les compilations :

- du noyau lui-même (make bzImage).
- des modules, si la compilation du noyau a réussi (make modules).
- et enfin, installer les modules (dans /lib/modules/) si leur compilation a réussi (make modules_install).

4.2.2. Les structures de contrôle

Deux catégories de structures programmatiques permettent de contrôler l'exécution des scripts soit conditionnellement, en fonction de tests ; ou en opérant via des boucles itératives qui permettront donc de répéter une action tant qu'une condition sera vérifiée.

4.2.2.1. Les tests

Le shell offre la possibilité d'exécuter des branchements conditionnels dans le flux d'exécution en fonction du résultat d'un ou plusieurs tests qui sont introduits par différentes syntaxes possibles :

  • soit par la commande interne [ [val1] op val2 ]

  • soit par la commande interne [[ [val1] op val2 ]]

  • ou encore par la commande externe test [val1] op val2

  • en ce qui concerne les expressions arithmétiques il faut utiliser soit la commande interne (( [val1] op val2 )) ou let

  • ou la commande externe expr [val1] op val2

Tableau 1. Opérateurs de tests portant sur les fichiers :

commandeopérateursignificationcompatibilité
test ou [ ou [[-a nomfic ou -e nomficVRAI si le fichier existeksh ou bash uniquement
test ou [ ou [[-s nomficVRAI si le fichier n'est pas videsh et suivants
test ou [ ou [[-f nomficVRAI si le fichier est de type ordinairesh et suivants
test ou [ ou [[-d nomficVRAI si le fichier est un répertoiresh et suivants
test ou [ ou [[-h nomficVRAI si le fichier est un lien symboliquesh et suivants
test ou [ ou [[-L nomficVRAI si le fichier est un lien symboliqueksh ou bash uniquement
test ou [ ou [[-b nomficVRAI si le fichier représente un périphérique blocsh et suivants
test ou [ ou [[-c nomficVRAI si le fichier représente un périphérique caractèressh et suivants
test ou [ ou [[-p nomficVRAI si le fichier est un tube nommésh et suivants
test ou [ ou [[-S nomficVRAI si le fichier est un socketksh ou bash uniquement
test ou [ ou [[-r nomficVRAI si le fichier est accessible en lecturesh et suivants
test ou [ ou [[-w nomficVRAI si le fichier est accessible en écrituresh et suivants
test ou [ ou [[-x nomficVRAI si le fichier est possède le droit d'éxécutionsh et suivants
test ou [ ou [[-u nomficVRAI si le fichier est possède le setuid-bitsh et suivants
test ou [ ou [[-g nomficVRAI si le fichier est possède le setgid-bitsh et suivants
test ou [ ou [[-k nomficVRAI si le fichier est possède le sticky-bitsh et suivants
test ou [ ou [[-x nomficVRAI si le fichier est possède le sticky-bitsh et suivants
test ou [ ou [[-O nomficVRAI si l'utilisateur est propriétaire du fichierksh ou bash uniquement
test ou [ ou [[-G nomficVRAI si l'utilisateur appartient au groupe propriétaire du fichierksh ou bash uniquement
test ou [ ou [[nomfic1 -nt nomfic2VRAI si le fichier nomfic1 est plus récent que le fichier nomfic2ksh ou bash uniquement
test ou [ ou [[nomfic1 -ot nomfic2VRAI si le fichier nomfic1 est plus ancien que le fichier nomfic2ksh ou bash uniquement
test ou [ ou [[nomfic1 -ef nomfic2VRAI si les fichiers nomfic2 et nomfic2 référencent la même inodeksh ou bash uniquement
test ou [ ou [[-t [descripteur]VRAI si le descripteur (1 par défaut) est associé à un terminalsh et suivants
Les tests peuvent également porter sur des chaines de caractères (valeurs de variables ou constantes).

Tableau 2. Opérateurs de tests portant sur les chaines de caractères :

commandeopérateursignificationcompatibilité
test ou [ ou [[-z chaineVRAI si la chaine est videsh et suivants
test ou [ ou [[-n chaine ou nomficVRAI si la chaine n'est pas videsh et suivants
test ou [ ou [[chaine1 = chaine2VRAI si les deux chaines sont égalessh et suivants
test ou [ ou [[chaine1 != chaine2VRAI si les deux chaines sont différentessh et suivants
[[chaine1 < chaine2VRAI si la chaine1 est lexicographiquement avant la chaine2ksh ou bash uniquement
[[chaine1 > chaine2VRAI si la chaine1 est lexicographiquement après la chaine2ksh ou bash uniquement
[[chaine1 = modèleVRAI si la chaine correspond au modèle (le modèle est une expression correspondant à la syntaxe des recherche de fichiers)ksh ou bash uniquement
[[chaine1 != modèleVRAI si la chaine diffère du modèleksh ou bash uniquement
expr ou ((chaine1 \& chaine2VRAI si les 2 chaines ne sont pas nullessh et suivants
expr ou ((chaine1 \| chaine2VRAI si l'une des 2 chaines n'est pas nullesh et suivants
expr ou ((chaine : expression régulièrecompare la chaine à l'expression régulière.sh et suivants
Ou enfin sur des nombres. En ce qui concerne les expressions arithmétiques, il faut utiliser soit les commandes (( ... )) ou expr dans la mesure où les précédentes commandes ne peuvent qu'effectuer des comparaison sur des valeurs numériques.

Tableau 3. Opérateurs de tests portant sur des nombres :

commandeopérateursignificationcompatibilité
test ou [ ou [[nb1 -eq nb2VRAI si les nombres sont égauxsh et suivants
test ou [ ou [[nb1 -ne nb2VRAI si les nombres sont différentssh et suivants
test ou [ ou [[nb1 -lt nb2VRAI si nb1 est strictement inférieur à nb2sh et suivants
test ou [ ou [[nb1 -le nb2VRAI si nb1 est strictement inférieur ou égal à nb2sh et suivants
test ou [ ou [[nb1 -gt nb2VRAI si nb1 est strictement supérieur à nb2sh et suivants
test ou [ ou [[nb1 -ge nb2VRAI si nb1 est strictement supérieur ou égal à nb2sh et suivants
expr ou ((nb1 + nb2additionsh et suivants
expr ou ((nb1 - nb2soustractionsh et suivants
expr ou ((nb1 * nb2multiplicationsh et suivants
expr ou ((nb1 / nb2divisionsh et suivants
expr ou ((nb1 % nb2modulosh et suivants
(( uniquement~nb1complément à 1ksh et bash uniquement
(( uniquementnb1 > nb2décalage à droite de nb2 bits sur nb1ksh et bash uniquement
(( uniquementnb1 < nb2décalage à gauche de nb2 bits sur nb1ksh et bash uniquement
(( uniquementnb1 & nb2ET bit à bitksh et bash uniquement
(( uniquementnb1 | nb2OU bit à bitksh et bash uniquement
(( uniquementnb1 ^ nb2OU EXCLUSIF bit à bitksh et bash uniquement
expr ou ((nb1 \> nb2VRAI si nb1 est strictement supérieur à nb2sh et suivants
expr ou ((nb1 \< nb2VRAI si nb1 est strictement inférieur à nb2sh et suivants
expr ou ((nb1 \>= nb2VRAI si nb1 est supérieur ou égal à nb2sh et suivants
expr ou ((nb1 \<= nb2VRAI si nb1 est inférieur ou égal à nb2sh et suivants
expr ou ((nb1 = nb2VRAI si nb1 est égal à nb2sh et suivants
(( uniquementnb1 == nb2VRAI si nb1 est égal à nb2ksh ou bash
(( uniquementnb1 [opération arithmétique]= nb2assignement (éventuellement précédé de l'opération)ksh ou bash
expr ou ((nb1 != nb2VRAI si nb1 est différent de nb2sh et suivants
expr ou ((-nb1opposé de nb1sh et suivants
expr ou ((-nb1opposé de nb1sh et suivants

Afin de compléter cette armada de tests, le shell dispose également d'opérateurs permettant d'influer sur un test ou lier logiquement plusieurs tests :

  • L'opérateur ! : signifie la négation du test le suivant.

  • L'opérateur -a : effectue un ET logique entre deux tests.

  • L'opérateur -o : effectue un OU logique entre deux tests.

Ces opérateurs ont un ordre de priorité décroissant, c'est à dire que ! sera évalué avant -a, qui sera lui-même évalué avant -o. Toutefois, cet ordre d'évaluation peut être imposé par l'utilisation des caractères de regroupement \( ... \).

La syntaxe de la commande [[ ... ]] s'est vue enrichie par rapport à la commande test (ou [ ... ]) basique, et notamment en ce qui concerne les chaines de caractères pour lesquelles l'usage des guillemets n'est plus nécessaire, certains opérateurs ont été rajoutés (cf. tableau) et les opérateurs de négation et de regroupement ont été alignés sur la syntaxe du C :

  • L'opérateur ! reste tel quel.

  • L'opérateur -a (ET logique) devient && (idem pour la commande(( ... ))).

  • L'opérateur -o (OU logique) devient || (idem pour la commande (( ... ))).

  • Et les parenthèses de regroupement ne doivent plus être échappées.

Exemples d'utilisation :

test -a lefichier; echo $? : teste l'existence du fichier lefichier.
[ -a lefichier ]; echo $? : teste l'existence du fichier lefichier.
[ -b /dev/sda ]; echo $? : teste si /dev/sda est un périphérique bloc.
[ "ch1" ="$ch2" ]; echo $? : teste l'égalité des deux variables chaine ch1 et ch2.
[ ch1 =$ch2 ]; echo $? : teste l'égalité des deux variables chaine ch1 et ch2.
[[ ch1 =$ch2 ]]; echo $? : teste l'égalité des deux variables chaine ch1 et ch2.
[ -w $fic1 -a \( -e $rep1 -o -e $rep2 \) ]; echo $? : devinez ...
[[ -w $fic1 && ( -e $rep1 || -e $rep2 ) ]]; echo $? : devinez ...
X=10; echo `expr $X + 10`; echo $?; echo $X : calcule X + 10, X reste identique.
X=10; echo $((($X + 10))); echo $?; echo $X : calcule X + 10, X reste identique.

4.2.2.2. Les branchements : structures if, elif et case

Les tests peuvent donc permettre d'éxecuter conditionnellement un partie d'un programme en fonction de son résultat. La structure pour procéder à ces branchements dans le code est la structure if : si la condition est vraie, alors le programme exécute un bloc de code. Mais elle peut se compliquer quelque peu en rajoutant les notions de sinon (else) et de sinon, si (elif). La syntaxe est la suivante :

if test
then
... bloc de code ...
[elif test]
then
... bloc de code ...
[else]
... bloc de code ...
fi

ou encore

 
if test ; then
... bloc de code ...
[elif test] ; then
... bloc de code ...
[else]
... bloc de code ...
fi

Exemples d'utilisation :

if [ -a ~/.bashrc ] ; then
echo "J'ai bien un fichier de configuration personnalisé pour le shell bash !"
echo "En voici son contenu :"
cat ~/.bashrc
fi
 
 
 
if [ -z $a ] ; then
echo "La variable a est indéfinie !"
exit 1
fi
if (( $a == 10)) ; then
echo "La variable a vaut $a !"
elif [ $a -eq 20 ] ; then
echo "La variable a vaut $a !"
elif test $a -lt 20 ; then
echo "La valeur de la variable a est inférieure à 20 ..."
echo "La valeur de la variable a est différente de 10 ..."
else
echo "La valeur de la variable a est supérieure à 20 ..."
fi

Dans certains cas, il sera possible de remplacer l'utilisation de nombreuses conditions if - elif qui deviennent rapidement illisibles par la structure case. Sa syntaxe est la suivante :

case $variable in
modele1)
... bloc de code ...
;;
modele2)
... bloc de code ...
;;
*)
... bloc de code ...
;;
esac

Le modèle peut contenir des caractères spéciaux qui serviront à construire un modèle générique :

Tableau 4. Caractères génériques servant à forger un modèle :

caractère spécialsignifiecompatibilité
*0 à n caractères quelconquessh et suivants
?1 caractère quelconquesh et suivants
[aAbBc]1 caractère parmis ceux figurant entre les crochetssh et suivants
[!aAbBc]1 caractère parmis ceux ne figurant pas entre les crochetssh et suivants
?(expression)0 ou 1 fois l'expression entre parenthèsesksh ou bash uniquement ; pour bash, il faut activer l'option shopt -s extglob
*(expression)0 à n fois fois l'expression entre parenthèsesksh ou bash uniquement ; pour bash, il faut activer l'option shopt -s extglob
+(expression)0 à n fois fois l'expression entre parenthèsesksh ou bash uniquement ; pour bash, il faut activer l'option shopt -s extglob
@(expression)1 fois fois l'expression entre parenthèsesksh ou bash uniquement ; pour bash, il faut activer l'option shopt -s extglob
!(expression)0 fois fois l'expression entre parenthèsesksh ou bash uniquement ; pour bash, il faut activer l'option shopt -s extglob

Exemple d'utilisation :

case "$1" in
start)
echo "Démarrage du daemon ..."
;;
stop)
echo "Arrêt du daemon ..."
;;
*)
echo "Usage : $SCRIPTNAME {start|stop}"
exit 3
;;
esac

4.2.2.3. Les boucles : structures for, while et until

Les boucles sont des structures de contrôle permettant d'itérer un bloc de code autant de fois que nécessaire. L'itération peut être déterminée soit par le nombre de valeurs d'une liste dans le cas de la boucle for, soit par le résultat d'un commande ou d'un test dans le cas des boucles while et until :

  • Le bloc code compris dans un boucle for sera exécuté pour chaque élement de la liste ; la liste peut être fournie explicitement, par substitution de variable, par susbtition de commande ou par évaluation des caractères spéciaux du shell. Si aucune liste n'est fournie, la boucle porte sur les arguments passés dans la ligne de commande (évaluation de la variable $*. Lors de chaque itération, la variable de boucle prend la valeur suivante dans la liste.

  • Le bloc code compris dans un boucle while sera exécuté tant que la valeur retournée par la commande de contrôle sera VRAI ; dès qu'elle retournera FAUX, le flux d'exécution passe à la commande suivant immédiatement le mot-clé done.

  • Le bloc code compris dans un boucle until sera exécuté tant que la valeur retournée par la commande de contrôle n'est pas VRAI.

La syntaxe d'une boucle for est la suivante :

for var [in liste]
do
... bloc de code ...
done
 
ou
 
for var [in liste] ; do
... bloc de code ...
done
 
 

La syntaxe d'une boucle while est la suivante :

while expression
do
... bloc de code ...
done
 
ou
 
while expression ; do
... bloc de code ...
done
 
 

La syntaxe d'une boucle until est la suivante :

until expression
do
... bloc de code ...
done
 
ou
 
until expression ; do
... bloc de code ...
done

Exemples d'utilisation :

for i in *; do ls -i; done;
for i in $(cat /etc/passwd | cut -d: -f1); do echo $i; done;
for i in `seq 1 10` ; do echo $i; done
for i in 1 2 3 4 5 6 7 8 9 10 ; do echo $i; done
 
while true ; do echo "Saisissez un chiffre"; read CHIFFRE; echo "$CHIFFRE erroné. essayez encore..."; done;
REP=0; while (( $REP < 10 )); do echo $REP; REP=`expr $REP + 1`; done;
 
until false ; do echo "Saisissez un chiffre"; read CHIFFRE; echo "$CHIFFRE erroné. essayez encore..."; done;
REP=0; until [ $REP -eq 10 ] ; do echo $REP; ((REP+=1)); done;

Lors de l'exécution d'une boucle, les commandes internes continue et break permettent de modifier le flux de commandes en, respectivement, passant à l'itération suivante et en interrompant le cours de la boucle.

Exemples d'utilisation :


for i in `seq 1 10`
do
	if (( $i == 5 ))
	then
		continue	# n'affichera pas 5
	fi
	if (( $i > 8 ))	# interromp la boucle pour i=9
	then
		break
	fi
	echo $i
done

clear
while true ; do         # boucle infinie 
        echo "Saisissez un nombre (0 non traité - 999 pour terminer)";
        read NOMBRE;
        if [  ! $(echo $NOMBRE | grep "^[ [:digit:] ]*$") ] ; then
                echo "On vous a demandé un nombre !"
                continue
        fi
        if [ $NOMBRE -eq 0 ] ; then
                continue
        elif [ $NOMBRE -eq 999 ] ; then
                break
        else
                echo "Vous avez saisi le nombre $NOMBRE (999 pour terminer)"; 
                sleep 5
                clear
        fi
done;
						

4.3. Les fonctions

Dernière forme de structuration du code, le shell prévoit la possibilité de création de fonctions qui permettent de factoriser des portions de code destinées à être exécutées plusieurs fois dans un script, ou bien encore de créer des librairies de code réutilisables dans différents scripts. Une fonction doit être définie avant son utilisation (son appel) ; la syntaxe pour la définition d'une fonction est la suivante :

nomdelafonction() {
... bloc de code ...
}
 
Une seconde forme est également possible (uniquement avec ksh ou bash)
 
function nomdelafonction {
... bloc de code ...
}

Quant à la syntaxe d'appel (utilisation) d'une fonction est tout simplement :

nomdelafonction [parametre1 parametre2 ...]

... à la condition que la fonction appelée ait été définie avant son appel, sous peine d'affichage d'une message d'erreur lors de l'exécution du script.

Une function, comme obéit aux mêmes principes que toute commande Unix : elle retourne un code de retour (un entier compris entre 0 et 255) qui vaut par défaut 0, mais cette valeur peut être modifiée en utilisant la commande interne return [valeur]. Ce code de retour est transmis à la variable spéciale $? dès le retour de la fonction, et peut donc être testé comme de coutume.

Les variables utilisateur sont globales pour l'ensemble d'un script, c'est à dire qu'une variable déclarée dans le script sera accessible et modifiable dans le corps d'une fonction, et qu'à l'identique une variable déclarée ou affectée dans le corps d'une fonction sera accessible au reste du script. Or, dans nombre de cas, les variables de travail d'une fonction n'ont à être accessibles ni publiées à l'extérieur de celle-ci ; la commande interne typeset permet de déclarer des variables locales dans une fonction : elle sont alors ni accessibles ni publiées en dehors du corps de la fonction.

Quant aux paramètres passés à une fonction, ils sont accessibles à celle-ci via le jeu de variables spéciales locales $1, $2, ..., $*, $@, $#.

Note

La variable spéciale $0 reste quant à elle accessible, en lecture-seule, et contient toujours le nom du script en cours d'exécution.

Exemples d'utilisation :

#!/bin/bash

function saisie_nombre {
        echo -n "$0> Saisissez un nombre (999 pour terminer) : "
        read NOMBRE;
         if [  ! $(echo $NOMBRE | grep "^[ [:digit:] ]*$") ] ; then
                NOMBRE=""
                return 1
        fi
}

clear
while true ; do         # boucle infinie
        saisie_nombre
        RES=$?
        if [ $RES -ne 0 ] ; then
                echo "On vous a demandé un nombre !"
                continue
        fi
        if [ $NOMBRE -eq 0 ] ; then
                continue
        elif [ $NOMBRE -eq 999 ] ; then
                break
        else
                echo "Vous avez saisi le nombre $NOMBRE (999 pour terminer)";
                sleep 5
                clear
        fi
done;