OS6 – Ecriture de scripts bash sous Linux
Il servent trois objectifs :
1.permettre à l'utilisateur de lancer simplement au clavier l'exécution des programmes stockés dans la mémoire de masse (les « commandes externes »).
2.autoriser l'utilisateur à personnaliser sa session de travail par le biais de variables d'environnement.
3.donner la possibilité à l'utilisateur d'écrire et de faire exécuter de véritables programmes nommés « scripts shell ». Ces scripts réalisent des enchaînements conditionnels de commandes permettant l'automatisation de traitements complexes.
Pour simplifier Linux fournit deux shells :
1.le « bash » – ou « bourne shell », shell officiel « GNU », qui est utilisé majoritairement pour écrire les scripts
2.le « csh » – ou « C shell », et le « tcsh » qui est un « csh » plus rapide et plus puissant. Ce shell propose un langage de programmation très proche du langage C.
Lorsqu'un utilisateur lance ou termine un shell certain scripts (voir ci-après) sont exécutés automatiquement par le shell (s'ils existent). Ces fichiers sont stockés dans le sous-répertoire personnel (« ~ ») de l'utilisateur ou dans le sous-répertoire « /etc ».
Un shell peut être lancé dans différents modes de connexion :
1.« login » : le shell est lancé lorsqu'un utilisateur se connecte au système,
2.« interactif » : le shell est lancé pour que l'utilisateur puisse entrer des commandes texte au clavier,
3.« non-interactif » : le shell est lancé pour exécuter un script.
Un script shell est un fichier texte contenant un programme pouvant être interprété (~ exécuté) par un shell.
Pour cela, il doit posséder l’attribut « x » d’exécution. On peut aussi lancer un script shell qui n'a pas l'attribut « x » en utilisant la commande source.
La première ligne d'un fichier script peut préciser par quel type de shell il doit être exécuté.
Exemple : pour forcer l'exécution du script par un shell « csh », sa première ligne doit être :
#!/bin/csh
Note : par défaut sous Linux, un script shell est exécuté par le « Bourne Shell » (voir ci-dessous)
Par défaut lorsqu'un utilisateur lance un terminal il lance l'exécution d'un bash en mode « interactif ».
On peut également dans un terminal existant (exécutant par exemple un autre shell comme le « csh ») – ou même un autre bash) lancer un bash également en mode « interactif » en entrant la commande bash .
Si on souhaite lancer l'exécution en arrière-plan d'un script – nommé par exemple « riri » (mode « non interactif »), on peut utiliser la commande suivante : bash riri &
Au lancement les scripts exécutés automatiquement dépendent du mode utilisé :
•mode « login » : « /etc/profile », puis le premier parmi « ~/.bash_profile », « ~/.bash_login » ou « ~/.profile ».
•mode « interactif » : « ~/bashrc »
•mode « non interactif » : les noms des scripts sont stockés dans la variable d'environnement « BASH_ENV » ; chaque nom de fichier (exprimé de manière absolu) est séparé du suivant par le caractère « ; ».
La terminaison d'un bash lancé en mode « interactif » s'effectue lorsque l'utilisateur entre la commande exit
En mode « non interactif » le bash se termine lorsque l'exécution du script qu'il interprète est terminée.
En mode « login » : le shell exécute le script « ~/.bash_logout » lors de la sortie de session.
Le bash utilise un grand nombre de variables pour son usage propre et permet également d'en définir de nouvelles.
Ces variables sont constituées d'un doublet (nom, valeur). Par défaut la valeur d'une variable est toujours une chaîne de caractères (bien que bash donne la possibilité de définir et d'utiliser des variables numériques entières).
Les variables sont conservées dans une zone de données associée au processus appelée espace d'environnement.
Pour un processus donné cet espace est scindé en deux parties distinctes :
1.la première est héritée du processus parent qui a lancé l'exécution du processus courant
2.la seconde est propre au processus courant.
Lorsqu'une nouvelle variable est créée dans un processus donné elle est stockée dans la deuxième partie de l'espace d'environnement.
Ceci signifie qu'elle ne sera transmise à aucun processus lancé à partir du processus courant.
Pour qu’une variable créée dans un shell soit passée aux processus lancés à partir de ce shell il faut utiliser une commande interne spécifique au bash nommée export suivie du nom de la variable
Exemple : exporter la variable nommée « fifi » : export fifi
Une variable est un couple (nom, valeur).
Pour définir une variable, il suffit de déposer une valeur dans cette variable ; pour cela il faut lier le nom de la variable et sa valeur par le signe « = ».
Exemple : pour déposer le texte « coucou » dans la variable « riri » : riri=coucou
ATTENTION : remarquez l'absence indispensable d'espaces autour du signe « = »
La valeur déposée dans la variable ne doit pas comporter d'espaces, dans le cas contraire il faut encadrer cette valeur par des guillemets.
Exemple : pour déposer le texte « coucou c'est moi » dans la variable « riri » : riri="coucou c'est moi" .
Pour afficher ou utiliser une variable il faut utiliser son nom précédé du caractère « $ ».
Exemple : pour afficher à l'écran la valeur de la variable « riri » : echo $riri
Pour modifier une variable on utilise également le signe « = » en plaçant à droite une nouvelle valeur, une expression ou la valeur d'une autre variable
Exemple : pour déposer la valeur de la variable « fifi » dans la variable « riri » : riri=$fifi .
On peut trouver ci-dessous différents cas d'affectation, de modification et d'affichage de variables :
bash$ var1=coucou
bash$ echo $var1
coucou
bash$ echo $VAR1
bash$ var1 =coucou
bash: var1: command not found
bash$ var1= coucou
bash: coucou: command not found
bash$ var1=
bash$ echo $var1
bash$ VAR1=$var1
bash$ echo $VAR1
coucou
bash$ var1=coucou ca va
>
CTRL/C
bash$ var1="coucou ca va"
bash$ echo $var1
coucou ca va
bash$
Les variables arguments sont des variables utilisées à l'intérieur d'un script et dont les valeurs sont positionnées avec les arguments fournis au script lors de son lancement en ligne de commande.
➢La variable de position « 0 » contient le nom du script lancé (« $0 » est le nom du script) ; la variable « 1 » contient le 1er argument passé à cette commande, la variable « 2 » le 2ème argument, etc.
➢La variable « # » contient le nombre d'arguments passés au script
➢La variable « * » contient l'ensemble des arguments passés au script ; son contenu est équivalent à la chaîne obtenue en concaténant les valeurs des arguments ("$1 $2 ... $n")
➢La variable « @ » est un tableau dont chaque case contient la valeur d'un argument.
Exemple : le script suivant (nommé « sc ») fournit des informations sur ses arguments de lancement (trace d'exécution à suivre) :
echo $#
echo $0 $1 $2
echo $*
bash$ sc coucou mon ami
3
sc coucou mon
sc coucou mon ami
bash$
S'il existe plus de 9 variables de position, on accède aux valeurs des arguments supplémentaires en utilisant la commande shell interne shift . L'effet de cette commande est d'effectuer un décalage des valeurs de variables en partant de la variable « 9 ».
Après la première exécution de « shift » la variable « 0 » contient la valeur de la variable « 1 », la « 2 » celle de la « 3 », et ainsi
de suite jusqu'à la « 9 » qui est alors positionnée avec la valeur du 10ème argument.
Le script ci-dessous :
1.effectue un premier affichage des variables de position qui lui sont passées,
2.puis appelle la commande shift et réaffiche le contenu des variables de position.
echo "valeurs des 9 premiers arguments"
echo $1 $2 $3 $4 $5 $6 $7 $8 $9
echo "Premier décalage"
shift
echo "nouvelles valeurs des 9 premiers arguments"
echo $1 $2 $3 $4 $5 $6 $7 $8 $9
Les traces d'exécution sont les suivantes :
bash$ ./script1 a b c d e f g h i j
valeurs des 9 premiers arguments
a b c d e f g h i
Premier décalage
nouvelles valeurs des 9 premiers arguments
b c d e f g h i j
bash$
Elles sont trois :
•La variable « $ » qui contient le PID (« processus id ») du shell courant
•La variable « ! » qui contient le PID du dernier travail lancé en arrière plan
•La variable « ? » qui contient le code de retour de la dernière commande
Une variable tableau est reconnaissable au crochets droits (« [ » et « ] ») qui suivent le nom de la variable.
L'expression à l'intérieur des crochets indique le numéro de la case du tableau concernée.
➢tab[0]=val : affectation de la 1ère case du tableau « tab »
➢${tab[0]} ou $tab : contenu de la 1ère case du tableau « tab »
➢${tab[11]} : contenu de la 12ème case du tableau « tab »
➢${tab[*]} : ensemble des cases du tableau « tab »
➢${#tab[11]} : longueur de la 12ème case du tableau « tab »
➢${#tab[*]} : nombre de cases du tableau « tab »
Les méta-caractères sont des caractères spéciaux qui possèdent une signification précise pour le bash.
Ce document a déjà introduit l'un de ces caractères : le caractère « $ » qui signifie « valeur de » lorsqu'il placé devant un nom de variable. Il en existe bien d'autres ; les plus simples sont présentés ci-après.
Le quoting consiste à encadrer des chaînes de caractères par :
1.des guillemets (« " »),
2.des « quotes » (« ' » – touche 4'{ )
3.des « backquotes » («`» – touche 7è` )
➢Permet de considérer une chaîne comprenant des espaces comme un tout.
➢Permet d'insérer des valeurs de variables à l'intérieur de chaînes constantes.
Exemple :
bash$ toto=bernard
bash$ echo "ca va $toto"
ca va bernard
bash$ echo "coucou 'ca va'"
coucou 'ca va'
bash$
•Permet de considérer une chaîne comprenant des espaces comme un tout.
•« déspécialise » tous les méta-caractères présents dans la chaîne.
Exemple :
bash$ toto=bernard
bash$ echo "ca va '$toto'"
ca va $toto
bash$ echo 'coucou "ca va"'
coucou "ca va"
bash$
Note : remarquez que cette fois « $toto » n'a pas été remplacé par la valeur de toto.
Permet d'évaluer, c'est à dire de récupérer, le résultat de l'exécution d'une commande ou d'un script.
Exemple : on souhaite afficher le sous-répertoire personnel de l'utilisateur précédé d'un commentaire (utilisation de la commande pwd ):
bash$ echo "rep = `pwd`"
rep = /home/albert
bash$
Elle est réalisée par le caractère « slash » (« \ »).
Lorsque le slash est placé devant un caractère comme un guillemet, une quote, une backquote ... ou un slash, ce caractère perd sa signification spéciale et est affiché comme un caractère « normal ».
Exemple :
bash$ echo "ca va \"toto\""
ca va "toto"
bash$ echo \toto
toto
bash$ echo \\toto
\toto
bash$
Le langage de programmation du bash utilise les commandes « internes » et « externes » ainsi que des boucles itératives ou décisionnelles afin de réaliser de véritables programmes.
C'est l'outil favori de l'administrateur Linux.
Nous avons vu plus haut que le contenu d'une variable shell est considéré par défaut comme du texte ; que faire dans ce cas pour effectuer des calculs dans un script shell ?
On peut « transformer » le contenu d'une variable contenant un nombre exprimé sous forme textuelle en une « vraie » valeur numérique en utilisant :
•soit l'opérateur « $((operation)) »
•soit le mot clé « let ».
L'exemple ci-dessous permet d'illustrer ce qui se passe dans différents cas de figure.
Exemple :
bash$ a=1
bash$ a=$a+1
bash$ echo $a
1+1
bash$ a=1
bash$ a=$(($a+1))
bash$ echo $a
2
bash$ a=1
bash$ let "a=$a + 1"
bash$ echo $a
2
bash$
Il n'existe pas d'opérateur ou de fonction native dans le bash pour tester si une variable est numérique ou pas.
Le script suivant indique le code à utiliser (à suivre les traces d'exécution dans différents cas ; la variable testée est celle passée en argument au script) :
if echo "$1" | grep -q -E '^[0-9]*[,]?[0-9]*$'
then
echo "$1 numérique"
else
echo "$1 non numérique"
fi
bash$ ./sc 1.04
1.04 non numérique
bash$ ./sc 1,04
1,04 numérique
bash$ ./sc 0,45
0,45 numérique
bash$ ./sc 0,45a
0,45a non numérique
bash$ ./sc 1.2
1.2 non numérique
Un test if permet de vérifier si une condition est vérifiée et d'exécuter un bloc d'instructions si c'est le cas (et éventuellement un autre bloc dans le cas contraire)
Note 1 : sous Linux quand une commande est réussie elle retourne la valeur « 0 », sinon elle retourne une valeur différente de « 0 ». Un test réussi se comporte de la même manière.
Note 2 : le résultat d'un test est disponible juste après l'exécution du test dans la variable spéciale « $ ? »
if condition
then instructions_si_condition_vraie
fi
ou
if condition
then instructions_si_condition_vraie
else instructions_si_condition_fausse
fi
Pour simplifier, la condition de test peut être exprimée :
•soit entre deux accolades droites (« [ »et « ] »),
•soit précédée de la commande test
ATTENTION : dans le cas où les accolades sont utilisées, un espace doit absolument séparer les accolades du contenu effectif de la condition.
« c1 », « c2 » peuvent être des variables contenant du texte ou des chaînes constantes ; « c » est une variable contenant du texte :
•[ $c1 = $c2 ]
vrai si c1 et c2 sont égaux
•[ $c1 != $c2 ]
vrai si c1 et c2 sont différents
•[ -z $c ]
vrai si c est la chaîne vide
•[ -n $c ]
vrai si c n'est pas la chaîne vide
Exemple : on donne ci-après le contenu d'un fichier script nommé « sc » et de son exécution :
c1=coucou
c2=cava
if [ $c1 != $c2 ]
then
echo différentes
else
echo pareilles
fi
bash$ sc
différentes
bash$
« n1 » et « n2 » contiennent des valeurs numériques.
•[ $n1 -eq $n2 ]
vrai si n1 et n2 sont égaux
•[ $n1 -ne $n2 ]
vrai si n1 et n2 sont différents
•[ $n1 -lt $n2 ]
vrai si n1 est strictement inférieur à n2
•[ $n1 -le $n2 ]
vrai si n1 est inférieur ou égal à n2
•[ $n1 -gt $n2 ]
vrai si n1 est strictement supérieur à n2
•[ $n1 -ge $n2 ]
vrai si n1 est supérieur ou égal à n2
Exemple : on donne ci-après le contenu d'un fichier script nommé « sc » et de son exécution :
n1=1
n2=2
if [ $n1 -le $n2 ]
then
echo "n1 <= n2"
else
echo "n1 > n2"
fi
bash$ sc
n1 <= n2
bash$
On peut également utiliser l'opérateur d'évaluation numérique « ((...)) » en utilisant les opérateurs « classiques » de comparaison numériques : « > », « < », « >= » et « <= ».
Exemple : on donne ci-après le contenu d'un fichier script nommé « sc » et de son exécution :
n1=1
n2=2
if (($n1 <= $n2))
then
echo "n1 <= n2"
else
echo "n1 > n2"
fi
bash$ sc
n1 <= n2
bash$
« e », « e1 » et « e2 » sont des expressions composées d'instructions, « v » est une variable.
•[ -e $v ]
vrai si v est défini
•[ ! e ]
vrai si e est faux
•[ e1 -a e2 ]
vrai si e1 et e2 sont vrais
[ e1 -o e2 ]
vrai si e1 ou e2 est vrai
« file », « file1 » et « file2 » sont des noms de fichier.
•[ -a file ]
vrai si file existe
•[ -d file ]
vrai si file est un sous-répertoire
•[ -f file ]
vrai si file est un fichier simple
•[ -r file ]
vrai si file est peut être lu
•[ -w file ]
vrai si file est peut être modifiés
•[ -x file ]
vrai si file est peut être exécuté
•[ file1 -nt file2 ]
vrai si file1 est plus récent que file2
•[ file1 -ot file2 ]
vrai si file1 est plus ancien que file2
Exemple : le script suivant fournit un grand nombre d'informations sur le fichier dont le nom lui est passé en argument :
if [ -e $1 ]
then
echo "Le fichier $1 est :"
if [ -f $1 ]
then
echo "un fichier normal"
if [ -s $1 ]
then
echo "taille > à 0"
fi
if [ -x $1 ]
then
echo "executable"
fi
fi
if [ -d $1 ]
then
echo "un repertoire"
fi
if [ -r $1 ]
then
echo "autorise en lecture"
fi
if [ -w $1 ]
then
echo "autorise en écriture"
fi
else
echo "Le fichier n'existe pas"
fi
Ce type de test cherche à comparer une expression avec un jeu de valeurs.
case expression in
exp1 ) liste d'instructions 1;;
exp2 ) liste d'instructions 2;;
...
* ) liste d'instructions ;;
esac
Note 1 : souvent expression sera une simple valeur de variable
Note 2 : « exp1 », « exp2 », etc. sont des valeurs pouvant être prises par expression.
Note 3 : liste d'instructions est une suite d'instructions séparées par le caractère point-virgule « ; »
Cette boucle s'interprète de la manière suivante :
•SI (expression = exp1) ALORS liste d'instructions 1 est exécutée
•SI (expression = exp2) ALORS liste d'instructions 2 est exécutée
•etc.
•SI expression ne prend aucune des valeurs « exp1 », « exp2 », etc. ALORS liste d'instructions defaut est exécutée
Note : la ligne de traitement par défaut (qui commence par « *) » est optionnelle.
Exemple : le script suivant lit un mot entré sur le clavier (à l'aide la commande « read ») et reconnaît le mot « chien » et le mot « carotte » en affichant s'il s'agit d'un animal ou d'un végétal ; s'il ne reconnaît pas le mot il affiche « inconnu ». :
echo "Entrez un mot"
read var
case $var in
chien) echo animal ;;
carotte) echo vegetal ;;
*) echo inconnu ;;
esac
Cette boucle permet de limiter à une valeur connue à l'avance le nombre de boucles (le nombre de boucles est donc majoré).
Cette syntaxe est adaptée au parcours d'une liste de valeurs.
for variable in liste_valeurs
do
liste instructions
done
Exemple : le script suivant affiche les arguments qui lui sont fournis lors de son lancement (suivi d'une trace d'exécution) :
for i in "$@"
do
echo "$i"
done
bash$ sc "coucou ca va" toto
coucou ca va
toto
bash$
Note 1 : si la première ligne du script avait été « for i in $@ » la trace d'exécution aurait été :
bash$ sc "coucou ca va" toto
coucou
ca
va
toto
bash$
Note 2 : si la première ligne du script avait été « for i in "$*" » la trace d'exécution aurait été :
bash$ sc "coucou ca va" toto
coucou ca va toto
bash$
Cette syntaxe est mieux adaptée à la réalisation d'un certain nombre de boucles.
for (( exp1 ; exp2 ; e3 ))
do
liste instructions
done
Note : exp1 », « exp2 » et « exp3 » sont des expressions arithmétiques .
La boucle :
•commence par exécuter « exp1 »
•puis TANT QUE « exp2 » est différente de zéro :
◦la liste d'instructions est exécutée
◦« exp3 » est évaluée.
Exemple : le script suivant affiche les trois nombres qui suivent l'argument qui lui est fourni lors de son lancement (suivi d'une trace d'exécution) :
for ((i=$1 ; $1+3 - $i ; i++))
do
echo $(($i + 1))
done
bash$ sc 5
6
7
8
bash$
Le nombre de boucles réalisé dans une boucle « while » n'est pas majoré ; la boucle s'exécute TANT QU'une condition « booléenne » n'est pas remplie !
while condition
do
liste instructions
done
Le script suivant vous aide à deviner un nombre choisi au hasard dans ce script :
g=$(($RANDOM % 100))
echo "nb entre 0 et 100 ? :"
read i
max=100
min=0
while [ $i -ne $g ]
do
if (($i > $g))
then
if (($i < $max));
then
max=$i;
fi
m=$(( ($max - $min)/2 + $min))
if (( $m < $g ))
then
echo "nb entre $m et $max ? :"
else
echo "nb entre $min et $m ? :"
fi
else
if (($i > $min));
then
min=$i;
fi
m=$(( ($max - $min)/2 + $min))
if (( $m < $g ))
then
echo "nb entre $m et $max ? :"
else
echo "nb entre $min et $m ? :"
fi
fi
read i
done
echo "bravo, le nombre est $g"
C'est la boucle complémentaire de « while » dans laquelle la boucle s'exécute JUSQU'A ce que la condition booléenne soit remplie.
Sa syntaxe est identique :
until condition
do
liste instructions
done
Ces opérations sont réalisées par la commande read .
Exemple : le script suivant vient lire une valeur au clavier, il la dépose dans une variable puis l'affiche :
read a
echo $a
La commande select permet d'afficher un menu de choix et de récupérer le choix effectué par l'utilisateur.
select choix in liste_de_choix
do
liste instructions
done
Le script suivant nommé « sc » demande à choisir parmi plusieurs fruits (deux traces d'exécution suivent) :
PS3='fruit ? '
select fruit in pomme orange poire banane
do
if [[ -n $fruit ]]; then
echo $fruit
break
else
echo 'mauvais choix'
fi
done
bash$ sc
1) pomme
2) orange
3) poire
4) banane
fruit ? 3
poire
bash$
bash$ sc
1) pomme
2) orange
3) poire
4) banane
fruit ? 9
mauvais choix
fruit ?
Note 1 : la variable PS3 positionnée à « fruit ? » contient l'invite à répondre utilisée par l'instruction « select ».
Note 2 : l'instruction break utilisée dans le script permet de « briser » une boucle « do - « done » dans tous les cas.
Dans les deux méthodes ci-dessous, chaque ligne du fichier est lue et déposée dans variable « ligne », puis affichée.
On utilise la commande cat , le méta-caractère « pipe » (« | » – touche 6-|) et la boucle while :
cat fichier | while read ligne
do
echo $ligne
done
On utilise la redirection du flux de sortie (« stdout ») avec le méta-caractère « < » et la boucle while :
while read ligne
do
echo $ligne
done < fichier
Dans l'exemple ci-dessous, chaque mot du fichier est lu et déposée dans variable « mot », puis affichée.
On utilise pour cela le quoting « ` », la commande cat et la boucle for :
for mot in `cat fichier`
do
echo $mot
done
L'instruction break permet de briser sans condition une boucle « do – done »
L'instruction continue permet de « réinitialiser » une boucle – c'est à dire de revenir directement à l'instruction qui débute la boucle.
http://www.tldp.org/LDP/Bash-Beginners-Guide/html/Bash-Beginners-Guide.html
http://abs.traduc.org/abs-5.0-fr/index.html
1Programme chargé de réaliser l'interface entre l'utilisateur et le système d'exploitation