Consignes
Le rendu du TP consistera en une archive (fichier .zip, .tar, etc.) comportant votre travail.
L'archive contiendra :
-
un fichier texte README contenant les réponses aux questions, vos remarques et commentaires
-
des fichiers ".sh" contenant les scripts demandés.
Le fichier README n'est pas un compte-rendu "classique". Vous devez produire un document synthétique du style "fiche de révision" contenant un résumé des notions et concepts que vous aurez explorés pendant le TP.
L'objectif est que vous produisiez une synthèse contenant les choses qui peuvent vous servir, par exemple pour l'examen !
Il n'est pas nécessaire de connaitre tous les détails par cœur, mais les techniques utilisées pour les 2 premiers tiers du TP sont au programme de l'examen.
Ce fichier contiendra obligatoirement une entête avec :
-
votre nom, votre filière et votre groupe,
-
le numéro de votre poste (si vous faites le TP sur un des ordinateurs de l'université).
Liens utiles
-
email des enseignants (Chambéry) damien.decout@univ-smb.fr, david.telisson@univ-smb.fr, gerald.cavallini@univ-smb.fr, jean-yves.ramel@univ-smb.fr, pierre.hyvernat@univ-smb.fr
-
email des enseignants (Annecy) Ammar.Mian@univ-smb.fr, andrew.mary-huet-de-barochez@univ-smb.fr, guillaume.ginolhac@univ-smb.fr, sebastien.monnet@univ-smb.fr
Préliminaires : script
Le shell (ou invite de commandes) permet d'interagir avec le système d'exploitation. Comme vous l'avez vu pendant le TP2, le shell agit comme un interpréteur : vous entrez une commande, le shell l'évalue, affiche le résultat et recommence. On parle de "read-eval-print loop" (REPL). (C'est le même principe que l'interpréteur Python que vous utilisez dans vos cours de programmation.)
Comme Python, le shell peut également lire des commandes dans un fichier texte, en général avec l'extension ".sh". Plutôt que de programme, on parle plutôt de script dans ce cas. Par exemple, GameShell (que vous avez utilisé au TP2) est essentiellement constitué d'un ensemble de scripts shell.
Dans ce TP, vous allez découvrir :
-
les instructions de programmation du shell :
-
variables et affectations
-
fonctions
-
conditionnelles (
if ... then ... else ...) -
boucles (
for ...ouwhile ...)
-
-
les scripts, c'est à dire l'utilisation de fichier pour conserver vos programmes shell.
Bien sûr, tous les concepts vus au TP2 comme les redirections restent valides dans un script.
-
Lancez un terminal, taper les commandes suivantes et observez le résultat
$ pwd --> que se passe t'il ??? $ cd --> que se passe t'il ??? $ ls --> que se passe t'il ??? $ cd /tmp --> que se passe t'il ??? $ pwd --> que se passe t'il ??? -
En utilisant un éditeur de texte, par exemple geany ou gedit, écrivez les même commandes dans un fichier appelé script1.sh dans votre répertoire de travail du TP :
pwd cd ls cd /tmp pwd -
Vérifiez que le fichier apparait avec la commande
ls$ ls README script1.sh ...(Si ce n'est pas le cas, utilisez cd pour vous déplacer dans le répertoire approprié.)
La commande
$ source script1.shdoit interpréter les lignes de votre fichier dans le shell courant.
Lancez cette commande et comparer le résultat avec ce que vous avez observé au point 1.
-
Les commentaires s'écrivent comme en Python : en les précédant d'un caractère #. Ajoutez un commentaire avec votre nom au début du fichier script1.sh et vérifiez qu'il produit le même résultat qu'auparavant.
Notez vos observations dans votre fichier README.
-
Si vous utilisez un autre shell que bash, il est possible que la commande
sourcene soit pas reconnue. Vous pouvez la remplacer par un point suivi d'un espace :$ . script1.sh -
Ceci est pratique si vous avez besoin d'exécuter fréquemment une suite de commandes un peu longue : il suffit de les mettre dans un fichier. Bien entendu, si le fichier n'est pas dans le répertoire courant, il faudra donner le chemin complet après
source.Si la suite de commandes est courte, il est également possible de les mettre sur une seule ligne en les séparant par des points virgules, dans le script, ou directement dans le shell :
$ pwd; cd; ls; cd /tmp; pwd
1. Variables et affectation
Il est difficile de programmer sans stocker des valeurs dans des variables. Les variables du shell servent essentiellement à stocker des chaines de caractères ou des nombres entiers.
-
L'affectation d'une valeur pour une variable se fait avec le signe
=:prenom="Pierre" nom="Hyvernat"ATTENTION, il ne faut pas mettre d'espace autour du signe
=. -
L'utilisation de la valeur d'une variable se fait en précédant le nom de la variable par le signe
$. Par exemple,prenom="Pierre" nom="Hyvernat" echo "$prenom"affichera simplement Pierre. (La commande
echoest un peu l'équivalent de la commandeprintde Python.)
N'oubliez pas les guillemets autour des chaines de caractères. Ils ne sont pas toujours obligatoire, mais leur absence peut entrainer des erreurs difficiles à repérer.
-
Essayez de deviner ce qui sera affiché par le script suivant:
prenom="Pierre" nom="Hyvernat" echo "Bienvenu $prenom" echo "Ton $nom est nom"Testez le script avec
$ source script2.shNotez attentivement ce qui se passe, et corrigez les erreurs présentes.
-
Remplacez, dans les lignes
echo ..., les guillemets double"..."par des guillemets simples'...'et relancez votre script. Comment interprêtez vous le résultat ?
Commentez et analisez les résultats des commandes suivantes dans votre fichier README :
$ nom="Hyvernat"
--> que passe t'il
$ nom
--> que passe t'il
$ $nom
--> que passe t'il
$ echo "$nom"
--> que passe t'il
-
Certaines variables sont définies automatiquement par le shell. Affichez les et cherchez à quoi elles correspondent :
-
HOME -
PWD -
OLDPWD -
SHELL -
USER
-
-
Testez l'effet de l'affectation suivante dans votre shell :
$ PS1="" --> que se passe t'ilTestez ensuite
$ PS1="><> "Expliquez ce qui se passe dans votre fichier README.
arithmétique
-
Testez le script suivant et analysez le résultat
p=97 q=73 echo "La multiplication de $p par $q est égale à $p*$q." -
Par défaut, les variables numériques sont en fait considérées comme des chaines de caractères. Pour effectuer des opérations arithmétiques (+, -, *, /, %), il faut mettre l'expression voulue entre
$((et)).Modifiez le script pour qu'il affiche effectivement
$ source script3.sh La multiplication de 97 par 73 est égale à 7081.
2. La boucle for
La boucle for du shell ressemble fortement à celle de Python. Sa syntaxe est
for VARIABLE in LISTE
do
...
done
Les points suivants sont importants :
-
L'indentation n'est pas significative (mais fortement conseillée), et le corps de la boucle se trouve entre les 2 lignes
doet
done -
VARIABLEest un nom de variable. Si cette variable existait déjà, sa valeur sera écrasée. -
LISTEest la liste des valeurs que l'on veut donner à la variable. Il s'agit d'une liste de chaines de caractères séparées par des espaces.
-
Testez le script suivant
for N in "pomme" "poire" "pain au chocolat" do echo "> $N" doneQue se passe t'il si vous oubliez les guillemets dans la liste
for N in ...? -
Remplacez la ligne
for N in ...parfor N in *. Lancez le script correspondant et expliquez son résultat. -
Modifiez le script pour qu'il affiche une liste numérotée des fichiers présents dans le répertoire courant.
Voila par exemple ce qu'il pourrait afficher :
$ source script4.sh 1> README 2> script1.sh 3> script2.sh 4> script3.sh 5> script4.sh 6> tmp
3. Conditionnelles
Comme tous les langages de programmation, le shell a une instruction
if. Sa syntaxe est
if COMMANDE
then
...
elif
then
...
elif
then
...
else
...
fi
ATTENTION : n'oubliez ni le then ni le
fi. Les elif et le else ne sont par
contre pas obligatoire.
COMMANDE est une commande du shell dont la valeur de retour sera
interprétée comme "vrai" ou "faux".
-
Le script suivant contient une conditionnelle toute simple :
if CONDITION then echo "C'est VRAI." else echo "C'est FAUX." fiRemplacez
CONDITIONpar-
0puis1 -
falsepuistrue
et décrivez attentivement ce qui se passe.
-
-
La valeur de retour d'une commande shell est stockée dans une variable spéciale
?. Sa valeur s'obtient donc avec$?.Affichez les valeurs de retour des commandes
trueetfalse.-
Qu'est-ce qu'une valeur vraie pour le shell ?
-
Qu'est-ce qu'une valeur fausse pour le shell ?
Décrivez ce que vous avez fait et les résultats dans votre fichier README.
-
-
Regardez les valeurs de retours des commandes suivantes :
$ ls ----> quelle valeur de retour ??? $ ls /tuhsoeat ----> quelle valeur de retour ???À quoi peut servir la valeur de retour ?
-
À votre avis, qu'affichera le script suivant ?
if echo "false" then echo "vrai" else echo "faux" fiTestez et expliquez ce que vous observez.
La plupart des tests "intéressants" se font avec la commande
test. Cette commande fait partie de la norme POSIX et est donc
présente sur la plupart des systèmes.
Par exemple, pour tester que $i est strictement plus petit que
10, on peut utiliser la commande
$ test "$i" -lt 10
("lt" : "less than").
-
Décrivez une manière de vérifier que
test "$i" -lt 10donne bien une valeur de retour "vraie" lorsque que la variableiest strictement plus petite que 10.Attention, si vous oubliez les guillemets et que la variable
in'existe pas (ou vaut la chaine vide), la commandetestprovoquera une erreur ! -
La page de manuel de la commande
testest disponible avec$ man testCherchez le test permettant de vérifier si une chaine de caractère est vide, et ajoutez un test dans le script suivant
prenom="" nom="Hyvernat" echo "Bienvenu $prenom" echo "Ton nom est $nom"pour que le premier affichage ne soit fait que lorsque la variable
prenomest non vide. -
La commande
testa un synonyme plus court, et souvent plus lisible :test UNE CONDITIONest équivalent à[ UNE CONDITION ].-
Vérifiez que la version courte fonctionne avec un test simple.
-
La commande
testse trouve dans le répertoire /usr/bin. Vérifiez que ce répertoire contient également un fichier [.
-
ATTENTION, si vous utilisez la version courte de test
avec les crochets, il ne faut pas oublier de mettre un espace après le
crochet ouvrant "[" et avant le crochet fermant
"]".
Les conditions complexes sont possibles en utilisant les options
-a ("and") et -o ("or") de la commande
test. Elles sont également possibles en utilisant les opérations
&& et || du shell :
-
COMMANDE1 && COMMANDE2exécute la première commande, puis exécute la seconde seulement si la valeur de retour de la première est "vraie". La valeur de retour finale est celle de la dernière commande exécutée et correspond donc au "et" booléen des 2 valeurs de retour. -
COMMANDE1 || COMMANDE2exécute la première commande, puis exécute la seconde seulement si la valeur de retour de la première est "fausse". La valeur de retour finale est celle de la dernière commande exécutée et correspond donc au "ou" booléen des 2 valeurs de retour.
Attention à ne pas confondre ces opérateurs avec &
(pour lancer une commande en tache de fond) et | pour rediriger
l'affichage d'une commande dans une autre.
Quelle différence y a t'il entre
-
test \( 10 -lt "$i" \) -a \( "$i" -lt 20 \) -
test 10 -lt "$i" && test "$i" -lt 20
Écrivez un script fichiers.sh qui parcourt le répertoire courant et
-
lorsqu'il trouve un répertoire, affiche REP: suivi du nom du répertoire,
-
lorsqu'il trouve un fichier non vide (de taille non nulle), affiche FICHIER: suivi du nom du fichier,
-
lorsqu'il trouve un fichier vide, affiche VIDE: suivi du nom du fichier,
-
dans tous les autres cas, affiche ???: suivi du nom.
Voici un exemple d'exécution
$ source fichiers-correction.sh
VIDE: fichier_vide.txt
FICHIER: fichiers-correction.sh
???: lien_casse
FICHIER: script1.sh
FICHIER: script2.sh
FICHIER: script3.sh
FICHIER: script4.sh
FICHIER: script5.sh
FICHIER: script6.sh
REP: tmp
Vous pouvez obtenir un répertoire, un fichier vide et un lien symbolique cassé avec
$ mkdir tmp
$ touch fichier_vide
$ ln -s /abcde lien_casse
4. Fonctions
Comme en Python, il est possible d'encapsuler du code dans des fonctions afin de pouvoir le réutiliser plus facilement. Ces fonctions peuvent être appelées dans le script, ou depuis le shell ayant chargé le script contenant la fonction.
Les fonctions sont définies avec
NOM_FONCTION() {
...
...
}
Contrairement à Python, on ne déclare ni ne donne de noms aux arguments de la fonction. La fonction définie pourra utiliser 1, 2 ou plus d'arguments. Elle pourra même n'en avoir aucun ! C'est donc au programmeur de gérer les arguments à la main. Pour cela, il dispose des variables suivantes :
-
$#: nombre d'arguments donnés à la fonction lors de l'appel -
$1: premier argument donné à la fonction (ou la chaine vide s'il n'y avait pas d'argument), -
$2: deuxième argument donné à la fonction (ou la chaine vide s'il n'y avait pas de deuxième argument), -
... jusqu'à
$9$pour le neuvième argument donné à la fonction (ou la chaine vide), -
$@: la liste des arguments donnés à la fonction, séparés par des espaces.
La variable $@ est particulièrement utile pour faire une boucle
sur les arguments de la fonction avec for x in "$@".
Pour appeler la fonction, il suffit d'écrire
$ NOM_FONCTION ARG1 ARG2 ...
Attention, il n'y a pas de parenthèses ou de virgule comme en Python.
Écrivez une fonction show_args avec le comportement suivant :
-
elle affiche le nombre total d'arguments reçus,
-
elle affiche chaque argument, dans l'ordre, avec son numéro.
Voici un exemple d'exécution :
$ source script_show_args.sh
$ show_args Il fait chaud et beau.
> 5 argument(s)
argument 1: Il
argument 2: fait
argument 3: chaud
argument 4: et
argument 5: beau.
$ show_args pomme poire "pain au chocolat"
> 3 argument(s)
argument 1: pomme
argument 2: poire
argument 3: pain au chocolat
Si les guillemets ne sont pas pris en compte (dans le second appel), vérifiez
votre boucle : il faut faire for x in "$@" plutôt que for x
in $@.
5. Un système de favoris pour le shell
5.1. Description
Lorsque je travaille dans un terminal, certains répertoires reviennent très
souvent. C'est par exemple le cas de mon dossier personnel. Le shell offre un
raccourci pour se déplacer dans le dossier personnel : la commande
cd sans argument.
Je ne peux par contre pas sauvegarder d'autre répertoires "favoris" pour pouvoir m'y déplacer facilement.
Nous allons créer un système de "favoris" pour bash, avec 4 fonctions :
-
A(Ajoute) pour sauvegarder un nouveau favori. Cette fonction prend un argument (une chaine sans espace) et ajoute le répertoire courant dans la liste de favoris. -
C(Change) pour se déplacer dans un répertoire favori. Cette fonction prend un argument (un chaine sans espace) et cherche dans vos favoris. Si le favori existe, la fonction change le répertoire de travail, sinon, elle ne fait rien. -
Xpour supprimer un favori de la liste. -
L(Liste) pour afficher la liste de tous les favoris.
Ces 4 fonctions seront définies dans un fichier favoris.sh.
Voici un exemple d'utilisation
$ source favoris.sh
$ pwd
/home/hyvernat/info201/TP/sujets/TP4
$ A info201-4
Le répertoire /home/hyvernat/info201/TP/sujets/TP4 est sauvegardé dans vos favoris.
raccourci : info201-4
$ cd
$ pwd
/home/hyvernat/
$ C info201-4
Vous êtes maintenant dans le répertoire /home/hyvernat/info201/TP/sujets/TP4.
$ pwd
/home/hyvernat/info201/TP/sujets/TP4
$ X info201-4
Le favoris "info201-4" est supprimé de votre liste.
$ cd
$ C info201-4
Le favoris "info201-4" n'existe pas.
5.2. Précisions
Chacune des 4 fonctions demandées ne fait que quelques lignes. N'hésitez pas à demander à votre encadrant si vous pensez que vous faites des choses trop complexes.
A: ajout de favoris
Vous devrez utiliser un fichier (caché) .favoris_bash qui sera créé dans votre dossier personnel. Ce fichier contiendra une ligne par favori. Chaque ligne contiendra
-
le nom du favori (une chaine sans espace),
-
un espace,
-
la chaine de caractères "->",
-
un espace,
-
le chemin absolu du répertoire correspondant.
Créez un fichier favoris.sh pour contenir les fonctions que vous allez écrire.
-
N'oubliez pas de mettre quelques commentaires avec au minimum vos noms, groupe et filière.
-
Définissez une variable shell
FAVqui prendra la valeur$HOME/.favoris_bash. Il s'agira du fichier qui contiendra vos favoris... -
Définissez une fonction
Aqui permettra de sauver un nouveau favori en affichant un petit message.Les points importants sont :
-
cette fonction utilisera son premier argument
$1comme nom du favori, -
le nom du répertoire courant est contenu dans la variable
$PWD, -
on peut ajouter une ligne dans le fichier des favoris (variable
$FAV) avec la commandeechoet une redirection>>. -
vous utiliserez la chaine -> pour séparer le nom du favori de son chemin d'accès.
-
-
Testez votre fonction. Par exemple :
$ source favoris.sh $ A info201-3 Le répertoire /home/hyvernat/info201/TP/sujets/TP3 est sauvegardé dans vos favoris. raccourci : info201-3 $ cd ~/Téléchargements $ A tel Le répertoire /home/hyvernat/Téléchargements est sauvegardé dans vos favoris. raccourci : tel $ cat ~/.favoris_bash info201-3 -> /home/hyvernat/info201/TP/sujets/TP4 tel -> /home/hyvernat/Téléchargements
L : lister les favoris
Ajoutez maintenant la fonction L qui permet d'afficher la liste
des favoris sauvegardés. Pour cela, il suffit d'afficher le contenu du fichier
$FAV...
Pour rendre l'affichage plus joli, vous pouvez utiliser la commande
column -t qui aligne le contenu d'un fichier par colonnes.
Remarque : cette fonction n'utilise aucun argument !
X : suppression d'un favori
Ajoutez maintenant la fonction X qui permet de supprimer le
favori donné en argument ($1).
Pour cela, le plus simple est d'utiliser la commande
grep -vw "^MOT" FICHIER
qui affiche toutes les lignes de FICHIER, sauf celles qui
commencent par MOT. (Attention, le ^ est
nécessaire.)
En redirigeant le résultat de cette commande dans un fichier (avec
>), vous pouvez supprimer un favori...
Attention, il n'est en général pas possible de rediriger le résultat d'une commande sur un fichier dans le même fichier. Par exemple,
$ grep "CHAINE" FICHIER > FICHIER
n'aura pas l'effet attendu car la redirection > FICHIER
supprime le contenu avant que la commande grep "CHAINE" FICHIER
ne commence.
C : "cd" dans un favori
La fonction la plus importante est C qui permet de se déplacer
dans un répertoire sauvegardé dans un favori.
L'objectif est de récupérer le chemin d'accès associé au favori donné en
argument (variable $1) et de le sauvegarder dans une variable
CHEMIN, puis de d'effectuer la commande
cd $CHEMIN
On peut récupérer ce chemin avec une commande complexe :
CHEMIN=$(COMMANDE)
La commande en question pourra par exemple utiliser
-
l'utilitaire
greppour ne regarder que la ligne qui commence par le bon favorigrep -w "^MOT" FICHIERn'affiche que les lignes du fichier qui commencent par
MOT -
l'utilitaire
cutqui permet de ne conserver que certaines colonnes des lignes d'un fichier.Remarque : chaque ligne du fichier de favoris est de la forme
FAVORI -> CHEMINoù
FAVORIne contient pas d'espace. Le chemin est donc constitué de la colonne numéro 3. (Ou, si CHEMIN contient des espaces, de toutes les colonnes à partir de la 3ème...) -
une redirection
CMD1 | CMD2pour faire agirCMD2sur le résultat affiché parCMD1.
Cette manière de procéder permet d'éviter de faire une boucle sur le fichier de favoris !
-
Que pensez vous de la programmation shell ? En particulier, essayez de comparer, en quelques lignes, les avantages et inconvénient des langages bash et Python.
Est-ce que les projets que vous avez fait seraient plus difficiles, ou plus facile si vous deviez les faire en Python ?
-
Donnez deux exemples (ou plus) de taches qui pourraient être "facilement" scriptables.
5.3. Pour aller plus loin
Essayer d'améliorer votre script, par exemple avec les fonctionnalités comme :
-
autoriser les noms de favori incomplets :
C info201utilisera le favoriinfo201-3s'il existe. La fonctionCdevra donc-
chercher les favoris "compatibles"
-
s'il n'y en a qu'un seul, elle l'utilise
-
s'il y a un favori "exact", elle l'utilise
-
sinon, elle affiche un message et ne fait rien
-
-
Autoriser une chaine pour la commande
Lafin de ne lister que les favoris contenant une chaine de caractères. -
etc.

