Les bases de la programmation shell
Présentation
Ce chapitre présente les fonctionnalités qui composent les bases de la programmation shell. Lorsque les scripts donnés en exemple ne sont pas compatibles avec l’interpréteur utilisé par le lecteur, nous invitons ce dernier à récupérer les exemples à partir de l’espace de téléchargement, qui sont fournis pour différents shells présentés dans ce livre.
Les variables utilisateur
Le shell permet de définir ou redéfinir des variables qui conditionnent l’environnement de travail de l’utilisateur. Il est également possible de définir d’autres variables, dites variables utilisateur, qui vont permettre de stocker des informations qui seront nécessaires à l’exécution d’un script.
1. Nommer une variable
Voici les règles à utiliser pour attribuer un nom à une variable :
-
Le premier caractère fait partie de l’ensemble [a-zA-Z_].
-
Les caractères suivants sont pris dans l’ensemble [a-zA-Z0-9_].
2. Définir une variable
Une variable est définie dès qu’elle est initialisée. Le contenu d’une variable est considéré par le shell comme une suite de caractères.
a. Affecter une valeur à une variable
Exemples
$ var1=mot1
$ echo $var1
mot1
$
Il ne faut pas mettre d’espace autour du symbole d’affectation : dans l’exemple suivant, le shell interprète var1 comme la commande à lancer, = et mot1 comme les deux arguments de la commande var1. Autrement dit, il n’interprète pas le signe = comme symbole d’affectation :
$ var1 = mot1
ksh: var1: not found
$
b. Affecter une valeur contenant au moins un espace
L’espace doit être protégé car c’est un caractère spécial du shell (séparateur de mots sur la ligne de commande).
Exemples
$ var2='mot1 mot2 mot3' #CORRECT
$ echo $var2
mot1 mot2 mot3
$ var2=mot1 mot2 mot3 #INCORRECT
ksh: mot2: not found
$
c. Variable indéfinie
Une variable qui n’a jamais été initialisée est vide.
Exemple
$ echo $vide
$
d. Retirer la définition d’une variable
La commande interne unset permet...
Substitution de commandes
Les caractères de substitution permettent de remplacer une commande par l’affichage résultant de son exécution. Ce mécanisme est utilisé pour insérer dans une ligne de commande Unix le résultat d’une autre commande.
Syntaxe récente
posix |
ksh |
bash |
commande argument1 $(commande) ... argumentn
Syntaxe originelle avec les apostrophes inversées (anti-quotes)
bourne |
posix |
ksh |
bash |
commande argument1 `commande` ... argumentn
Premier exemple
Les commandes uname et logname sont remplacées par leur résultat avant exécution de la commande echo.
Syntaxe récente (bash, ksh, posix) :
$ echo Vous êtes connecté sur la machine $(uname -n)
et vous êtes $(logname)
Vous êtes connecté sur la machine rumba et
vous êtes christie
Syntaxe originelle :
$ echo Vous êtes connecté sur la machine `uname -n` et vous êtes `logname`
Vous êtes connecté sur la machine rumba et vous êtes christie
Deuxième exemple
Initialisation d’une variable monuid avec l’uid de l’utilisateur christie :
$ grep christie /etc/passwd
christie:x:2025:2000::/home/christie:/bin/bash
$
$ grep christie /etc/passwd | cut -d: -f3
2025
$
$ monuid=$(grep christie /etc/passwd | cut -d: -f3)
ou
$ monuid=`grep christie /etc/passwd | cut -d: -f3`
$
$ echo $monuid
2025
$
Caractères de protection
Les caractères de protection servent à faire perdre la signification des caractères spéciaux du shell. Il existe trois jeux de caractères ayant chacun leur fonctionnalité propre.
1. Les apostrophes (simples quotes)
Les apostrophes (ou simples quotes) retirent la signification de tous les caractères spéciaux du shell. Les apostrophes doivent être en nombre pair sur une ligne de commande.
Les apostrophes ne se protègent pas elles-mêmes.
Exemples
La variable $HOME est substituée par sa valeur :
$ echo $HOME
/home/christie
Le caractère $ perd sa signification spéciale :
$ echo '$HOME'
$HOME
Le caractère * est substitué par les noms de fichier du répertoire courant :
$ echo *
f1 f2 f3
Le caractère * perd sa signification spéciale :
$ echo '*'
*
Le shell s’attend à trouver un nom de fichier derrière une redirection :
$ echo >
bash: syntax error near unexpected token `>'
Le caractère > perd sa signification spéciale :
$ echo '>'
>
Le shell exécute la commande logname et la remplace par son résultat :
$ echo Bonjour $(logname)
Bonjour christie
La séquence de caractères $( ) perd sa signification spéciale :
$ echo 'Bonjour $(logname)'
Bonjour $(logname)
$
Protection de plusieurs caractères spéciaux :
$ echo '* ? > < >> << | $HOME $(logname) &'
* ? >< >> << | $HOME $(logname) &
$
L’apostrophe ne se protège pas elle-même. Pour le shell, la commande n’est pas terminée. Il affichera le prompt secondaire (PS2) tant que les apostrophes seront en nombre impair :
$ echo 'Aujourd'hui il fait beau'
>...
Récapitulatif des caractères spéciaux
Ce tableau regroupe les caractères spéciaux du shell vus précédemment.
Caractères |
Signification |
espace - tabulation - saut de ligne |
Séparateurs de mots sur la ligne de commande |
& |
Arrière-plan |
| << < > >> |
Tube et redirections |
() et {} |
Regroupement de commandes |
; |
Séparateur de commandes |
* ? [ ] ?() +() *() !() @() |
Caractères de génération de noms de fichier |
$ et ${ } |
Valeur d’une variable |
`` $() |
Substitution de commandes |
’ ’ " " \ |
Caractères de protection |
Interprétation d’une ligne de commande
Les caractères spéciaux du shell sont interprétés dans un ordre précis :
Séquence d’interprétation |
Commande interne |
Commande externe |
1. Isolement des mots séparés par les caractères, espace, tabulation, saut de ligne 2. Traitement des caractères de protection (’ ’ , " ", \). 3. Substitution des variables ($) 4. Substitution de commandes (`` $()) 5. Substitution des caractères de génération de noms de fichiers (*, ?, [] etc.) |
Effectué par le shell courant |
|
6. Traitement des tubes et redirections 7. Lancement de la commande |
shell courant |
shell enfant |
Exemple
Écriture et lancement d’un script shell
1. Définition
Un script shell est un fichier texte contenant des commandes Unix internes ou externes ainsi que des mots-clés du shell.
Il n’y a pas de convention imposée pour le nom d’un script shell. Le nom d’un fichier script shell peut avoir une extension, mais ce n’est pas obligatoire. Néanmoins, il est assez fréquent de choisir l’extension ".sh" (même si le script n’est pas interprété par un exécutable nommé "sh").
Dans le but d’améliorer la lisibilité, les noms des scripts utilisés dans ce chapitre auront une extension en relation avec leur compatiblité : .bash, .ksh, .sh, .posix, .bourne ...
Exemple
Voici le script premier.sh :
$ nl premier.sh
1 pwd
2 cd /tmp
3 pwd
4 ls
$
Exécution du script :
$ ksh premier.sh
/home/christie
/tmp
f1 f2 f3
$
Les commandes sont exécutées séquentiellement.
2. Exécution d’un script par un shell enfant
Dans la majorité des cas, les scripts doivent être exécutés par l’intermédiaire d’un shell enfant. Ceci a pour avantage de ne pas modifier l’environnement du shell courant. Pour lancer un script shell, il existe trois méthodes qui produiront un résultat équivalent.
Première méthode
$ ksh premier.sh
C’est la méthode utilisée précédemment. On appelle la commande ksh en lui demandant d’interpréter le script premier.sh. Dans ce cas, la permission de lecture est suffisante sur le fichier premier.sh :
$ ls -l premier.sh
-rw-r--r-- 1 christie cours 19 nov 15 19:16 premier.sh
Deuxième méthode
$ ksh < premier.sh
Le ksh est un programme qui lit sur son entrée standard...
Variables réservées du shell
Dans un script, un certain nombre de variables réservées sont accessibles en lecture. Ces variables sont initialisées par le shell et véhiculent des informations de nature diverse.
1. Les paramètres positionnels
bourne |
posix |
ksh |
bash |
Les scripts shell sont capables de récupérer les arguments passés sur la ligne de commande à l’aide de variables spéciales, nommées paramètres positionnels.
-
$# représente le nombre d’arguments reçus par le script.
-
$0 représente le nom du script.
-
$1 représente la valeur du premier argument, $2 la valeur du deuxième et ceci jusqu’à $9 qui représente la valeur du neuvième argument. Le ksh et le bash permettent d’utiliser les variables spéciales ${10}, ${11}, etc. Les accolades sont obligatoires lorsque le nom de la variable contient plus d’un chiffre.
-
$* et $@ représentent la liste de tous les arguments (la différence entre $* et $@ est présentée au chapitre Aspects avancés de la programmation shell - Comparatif des variables $* et $@).
Exemple
Voici un script qui affiche la valeur de chaque paramètre positionnel :
$ vi monscript.sh
echo "Ce script a recu $# arguments"
echo "Le nom du script est : $0"
echo "Mon 1er argument est : $1"
echo "Mon 2ème argument est : $2"
echo "Mon 3ème argument est : $3"
echo "Voici la liste de tous mes arguments : $*"
$ chmod u+x monscript.sh
Appel de monscript.sh avec six arguments (cf. Figure 4) :
$ monscript.sh f1 f2 f3 f4 /tmp/fic.txt 123
Ce script a recu 6 arguments
Le nom du script est : monscript.sh
Mon 1er argument est : f1
Mon 2ème argument est : f2
Mon 3ème...
La commande read
1. Syntaxe
Première syntaxe
bourne |
posix |
ksh |
bash |
read var1
read var1 var2 ...
Autres syntaxes
Ces syntaxes particulières (non portables) permettent l’affichage d’un message :
ksh |
read var?"Saisir une valeur : "
bash |
read -p "Saisir une valeur : " var
2. Lectures au clavier
La commande read lit son entrée standard et affecte les mots saisis dans la ou les variables dont le nom est passé en argument. La liste des caractères séparateurs de mots utilisés par read sont stockés dans la variable d’environnement IFS (elle contient par défaut les caractères espace, tabulation (\t) et saut de ligne (\n)).
Exemples
Le mot saisi est stocké dans la variable var1 :
$ read var1
bonjour
$ echo $var1
bonjour
Tous les mots saisis sont stockés dans la variable var1 :
$ read var1
bonjour tout le monde
$ echo $var1
bonjour tout le monde
Le premier mot est stocké dans var1, le deuxième dans var2 :
$ read var1 var2
Au revoir
$ echo $var1
Au
$ echo $var2
revoir
Le premier mot est stocké dans var1 et tout le reste de la ligne dans var2 :
$ read var1 var2
Au revoir tout le monde
$ echo $var1
Au
$ echo $var2
revoir tout le monde
Le mot est stocké dans var1 et var2 est vide :
$ read var1 var2
Merci
$ echo $var1
Merci
$ echo $var2
$
Spécifier un message d’invite avec la commande read en bash :
$ read -p "Entrez une valeur: " var1
Entrez une valeur: Hello
$ echo $var1
Hello
La commande read lit toujours une ligne complète (saut de ligne exclu).
3. Code de retour
La commande read renvoie un code vrai si elle ne reçoit pas l’information Fin de fichier (matérialisée par ˆd au clavier).
Exemples...
Exécution de tests
1. Introduction
Il existe deux commandes qui permettent de réaliser des tests :
-
La commande historique du Bourne shell [ ], utilisable également sous le nom de test. Elle est compatible avec les shells Bourne, ksh et bash. Elle est normalisée POSIX.
-
La commande [[ ]]disponible en ksh et bash. Il s’agit d’un sur-ensemble de la commande originelle [ ] du Bourne shell, avec toutefois quelques incompatibilités. Bien que la commande, [[ ]] ne soit pas normalisée POSIX, elle corrige les failles de [ ] et comporte des fonctionnalités supplémentaires intéressantes. Nous présenterons cette commande en premier.
2. La commande [[ ]]
ksh |
bash |
Cette commande permet de faire des tests sur des fichiers, des chaînes de caractères et des nombres. Elle renvoie le code 0 ou 1 (vrai ou faux) que l’utilisateur peut consulter en faisant afficher la valeur de $? ou bien exploiter avec les structures de contrôle if, while, until et les opérateurs logiques du shell && et ||.
a. Syntaxes
[[expression]]
ou
[[ expression ]]
Les espaces entourant l’expression présente à l’intérieur des crochets sont facultatifs.
Principe de fonctionnement
/etc/passwd est un fichier ordinaire (option -f ), donc la commande renvoie vrai :
$ [[ -f /etc/passwd ]]
$ echo $?
0
Utilisation de la structure de contrôle if
La structure de contrôle if est présentée ici brièvement dans le but d’illustrer l’utilisation concrète de la commande [[ ]].
if [[ -f /etc/passwd ]]
then
echo "Le fichier /etc/passwd est un fichier ordinaire"
else
echo "Le fichier /etc/passwd n'est pas un fichier ordinaire"
fi
Le principe de la structure de contrôle if est le suivant : la commande située...
Les opérateurs du shell
Ces opérateurs permettent d’exécuter ou non une commande en fonction du code de retour d’une autre commande. L’évaluation est faite de gauche à droite.
Il ne faut pas confondre les opérateurs du shell (&& et ||) qui effectuent une opération logique entre deux commandes Unix avec les opérateurs de la commande [[ ]] (&& et ||) qui sont internes à cette dernière.
Opérateur |
Signification |
&& |
ET logique |
|| |
OU logique |
! |
Négation (posix, ksh93, bash. Non documenté en ksh88 mais fonctionne) |
1. Évaluation de l’opérateur &&
Syntaxe
commande1 && commande2
-
La deuxième commande est exécutée uniquement si la première commande renvoie un code vrai.
-
L’expression globale est vraie si les deux commandes renvoient vrai.
Exemples
Le répertoire /tmp/svg n’existe pas, la commande cd n’est donc pas exécutée :
$ ls -d /tmp/svg
/tmp/svg: No such file or directory
$ pwd
/export/home/christie
$ [[ -d /tmp/svg ]] && cd /tmp/svg
$ echo $? # Code de la commande [[ ]]
1
$ pwd
/export/home/christie
Le répertoire /tmp/svg existe, la commande cd est donc exécutée :
$ mkdir /tmp/svg
$ [[ -d /tmp/svg ]] && cd /tmp/svg
$ pwd
/tmp/svg
$
Ces actions peuvent également être implémentées avec la structure de contrôle if.
$ pwd
/export/home/christie
$ ls -d /tmp/svg
/tmp/svg
$ if [[ -d /tmp/svg ]]
> then # Prompt PS2 du shell
> cd /tmp/svg
> fi # Fermeture du if: lancement de la commande
$ pwd ...
L’arithmétique
Les shells permettent nativement de réaliser des calculs avec les nombres entiers. L’arithmétique sur les nombres entiers est présentée au travers des commandes (( )) et let, spécifiques ksh et bash. La commande originelle expr du Bourne shell est ensuite traitée.
1. La commande (( ))
ksh |
bash |
a. Syntaxe
((expression_arithmétique))
ou
(( expression_arithmétique ))
b. Opérateurs
Elle reprend une grande partie des opérateurs du langage C.
Opérateurs |
Signification |
Opérateurs arithmétiques |
|
nb1 + nb2 |
Addition |
nb1 - nb2 |
Soustraction |
nb1 * nb2 |
Multiplication |
nb1 / nb2 |
Division |
nb1 % nb2 |
Modulo |
nb1++ |
Incrémente nb1 de 1 (bash/ksh93) |
nb1-- |
Décrémente nb1 de 1 (bash/ksh93) |
Opérateurs travaillant sur les bits |
|
~nb1 |
Complément à 1 |
nb1 >> nb2 |
Décalage sur nb1 de nb2 bits à droite |
nb1 << nb2 |
Décalage sur nb1 de nb2 bits à gauche |
nb1 & nb2 |
ET bit à bit |
nb1 | nb2 |
OU bit à bit |
nb1 ^ nb2 |
OU exclusif bit à bit |
Opérateurs de comparaison |
|
nb1 > nb2 |
Vrai si nb1 est strictement supérieur à nb2 |
nb1 >= nb2 |
Vrai si nb1 est supérieur ou égal à nb2 |
nb1 < nb2 |
Vrai si nb1 est strictement inférieur à nb2 |
nb1 <= nb2 |
Vrai si nb1 est inférieur ou égal à nb2 |
nb1 == nb2 |
Vrai si nb1 est égal à nb2 |
nb1 != nb2 |
Vrai si nb1 est différent de nb2 |
Opérateurs logiques |
|
!nb1 |
Inverse la valeur de vérité de nb1 |
&& |
ET |
|| |
OU |
Opérateurs divers |
|
-nb1 |
Opposé de nb1 |
nb1 = expression |
Assignement |
(expression) |
Regroupement |
nb1 binop= nb2 |
binop représente l’un des opérateurs suivants : +, -, *, /, %, >>, <<, &, |, ^. Cette écriture est équivalente à : nb1 = nb1 binop nb2 |
Exemples...
Substitution d’expressions arithmétiques
bourne |
posix |
ksh |
bash |
Les caractères de substitution de commandes ont été présentés dans la section Substitution de commandes de ce chapitre. Les caractères spéciaux du shell $(( )) permettent de substituer une expression arithmétique par son résultat. Ces caractères peuvent être placés entre guillemets.
Syntaxe
posix |
ksh |
bash |
commande argument1 $((expression-arithmétique)) ... argumentn
Exemple
$ x=2
Afficher la valeur de x+1, sans modifier la valeur de la variable (qui vaut toujours 2):
$ echo $((x+1))
3
Affectations :
$ i=1
$ i=$(( $i * 100 )) # ou i=$(( i * 100 ))
$ echo $i
100
Equivalent à :
$ i=`expr $i \* 100` ou i=$(expr $i \* 100)
Modifier et afficher la variable x :
$ echo "x vaut : $((x=x+1))"
x vaut : 3
$ echo $x
3
Il ne faut pas confondre avec la commande (( )) qui n’affiche rien :
$ x=2
$ ((x=x+1)) modification de la variable x
$ echo $x
3
Ne pas confondre (( )) et $(( )). (( )) est une commande interne au shell et $(( )) sont des caractères spéciaux du shell au même titre que `` ou $().
Arithmétique sur les flottants
Seul le ksh93 propose une fonctionnalité native pour travailler avec des nombres flottants.
1. ksh93
La commande typeset permet de déclarer un nombre flottant avec la précision souhaitée.
Syntaxe
typeset -Fprecision var1[=val1] [ var2=[val2] ... ]
Exemple
$ typeset -F3 tva=0.196
$ typeset -F2 ht=153
$ typeset -F2 montant Tva
$ montantTva=$(($ht * $tva ))
$ echo $montant Tva
29.99 # Arrondi de 29.988
2. Autres shells
La commande bc permet de réaliser des calculs et se comporte comme un filtre. Il est également possible d’utiliser la commande awk (nawk sur certaines plates-formes) qui sait effectuer des calculs sur les nombres flottants.
Exemple
Envoi d’une expression arithmétique à la commande bc :
$ ht=153
$ tva=0.196
$ echo "$ht * $tva" | bc
29.988
Envoi de deux paramètres à la commande awk (cf. chapitre Le langage de programmation awk) :
$ echo "$ht $tva" | awk '{ print $1 * $2 }'
29.988
Même chose que ci-dessus avec formatage :
$ echo "$ht $tva" | awk '{ printf("%.2f\n", $1 * $2) }'
29.99
Attention aux arrondis des valeurs limites, aussi bien avec typeset qu’avec printf : par exemple, le nombre 2.255 pourra être arrondi à 2.25 et non 2.26.
Mise au point d’un script
Le shell propose quelques options permettant de mettre au point des scripts shell.
1. Option -x
L’option -x permet de visualiser les commandes telles qu’elles sont exécutées, c’est-à-dire après traitement des caractères spéciaux du shell.
Première syntaxe
bourne |
posix |
ksh |
bash |
Activer l’option :
set -x
Désactiver l’option :
set +x
Deuxième syntaxe
posix |
ksh |
bash |
Activer l’option :
set -o xtrace
Désactiver l’option :
set +o xtrace
Troisième syntaxe
Invoquer le shell interpréteur avec l’option -x :
$ bash -x script
Exemple
Voici le script exist_fic.sh dans lequel une erreur a été introduite. Le développeur du script a, par inadvertance, écrit fic au lieu de $fic (ligne 5) :
$ nl exist_fic.sh
1 #! /usr/bin/bash
2 # compatibilité du script : bash
3 echo -n "Nom du fichier à visualiser: " # spécifique bash
4 read fic
5 if [[ -f fic ]] ; then
6 echo "$fic existe"
7 else
8 echo "$fic inexistant"
9 fi
Exécution du script sans mise au point. Il semble surprenant (voire inquiétant ?) que le fichier /etc/passwd ne soit pas trouvé !
$ exist_fic.sh
Nom du fichier a visualiser: /etc/passwd
Fichier inexistant
Exécution du script en activant l’option -x. Ici l’option est passée en argument au shell interpréteur du script. Les lignes affichées pour la mise au point sont précédées d’un...
Les structures de contrôle
1. if
La structure de contrôle if permet de réaliser des tests. La commande placée derrière le mot if est exécutée. En fonction du code renvoyé par celle-ci, le shell oriente le flux d’exécution dans la partie then si la commande a renvoyé vrai ($? vaut 0), dans la partie else si la commande a renvoyé faux ($? > 0). Si la commande renvoie faux et qu’il n’y a pas de partie else, le flux d’exécution se poursuit à la première commande située sous le fi.
Première syntaxe
if commande1
then
commande2
commande3
...
else
commande4
...
fi
Deuxième syntaxe
La partie else est facultative.
if commande1
then
commande2
commande3
...
fi
Troisième syntaxe
Il est possible d’utiliser le mot-clé elif qui signifie sinon si.
if commande1
then
commande2
...
elif commande3
then
commande4
... else
commande5
...
fi
Le mot-clé fi représente la fermeture du if. Le mot-clé elif n’a pas de fermeture.
Autres syntaxes
Le mot-clé then peut être placé sur la première ligne à condition d’utiliser un ; pour le séparer de la commande.
if commande1 ; then
commande2
...
fi
Il est également possible d’imbriquer des structures de contrôle. La syntaxe ci-dessous est équivalente à la syntaxe elif présentée...
Exercices
1. Variables, caractères spéciaux
a. Exercice 1 : variables
1. |
Définir une variable contenant votre prénom. Afficher cette variable. |
2. |
Définir une variable contenant votre prénom, un espace puis votre nom. Afficher cette variable. |
3. |
Supprimer ces deux variables (les rendre indéfinies). |
b. Exercice 2 : variables
Définir une variable contenant votre nom, une autre contenant votre prénom. Avec un seul echo, afficher ces deux variables, séparées par un caractère souligné (votreprenom_votrenom).
c. Exercice 3 : substitution de commande
1. |
En une seule commande, afficher la date courante :
|
2. |
Même chose que ci-dessus, mais formater la date comme ceci :
|
d. Exercice 4 : caractères de protection
Le répertoire courant contient les fichiers f1, f2 et f3 :
$ ls
f1 f2 f3
Que vont répondre les commandes suivantes :
1. |
|
2. |
|
3. |
|
4. |
|
5. |
|
6. |
|
7. |
|
8. |
|
9. |
|
10. |
|
2. Variables, affichages et lectures clavier
a. Exercice 1 : variables
Écrire un script premier.sh et réaliser les opérations suivantes :
-
Initialiser une variable prenom.
-
Initialiser une variable maDate qui contiendra la date courante.
-
Afficher ces deux variables.
Exécuter ce script.
b. Exercice 2 : paramètres positionnels
Commandes filtres utiles : wc (cf. chapitre Les commandes filtres).
Écrire un shell script wcount.sh qui produit le résultat suivant :
$ bash wcount.sh ours oiseau
Le nom du script est : wcount.sh
Le 1er argument est : ours
Le 2eme argument est : oiseau
Tous les arguments...