Ce TP comme ceux à venir devront être effectués sur un système UNIX. Ces deux parties sont estimée à 1h30 chacune.
Le but de cet exercice est d’écrire un script nommé cpt.sh qui incrémente chaque seconde le nombre contenu dans un fichier. Le script que vous mettez en œuvre dans cet exercice utilise un idiome (c.-à-d. un patron de programmation bash) que vous allez souvent retrouver dans les exercices qui suivent. Ce canevas est le suivant :
#! /bin/bash
# traite le cas dans lequel les arguments du script sont erronés
if <les arguments sont incorrects> ; then
echo "Un message d’erreur adequat" >&2 # &2 est la sortie d’erreur
exit 1 # un code de retour faux
fi
# ici, vous mettez le corps du script
# le script s’est exécuté correctement, le script renvoie un code de retour vrai
exit 0
Dans la question 1.a, on vous demande de traiter le cas dans lequel les arguments sont erronés. Dans les questions 1.b et 1.c, on vous demande de remplir le corps du script : initialiser le fichier passé en argument en y écrivant un 0, puis incrémenter le compteur contenu dans le fichier chaque seconde.
Q1.a: Dans un premier temps, nous allons traiter le cas dans lequel les arguments du script sont invalides. Les arguments sont invalides si :
Il n’y a pas un et un seul argument ou
Si l’unique argument est un chemin vers un répertoire existant (le test correspondant est [ -d chemin ]
).
Écrivez un script nommé cpt.sh qui renvoie faux (valeur 1) après avoir affiché un message d’erreur adéquat si les arguments sont invalides, et qui renvoie vrai (valeur 0) sinon.
Q1.b: Dans le corps du script, écrivez la valeur 0 dans le fichier passé en argument (“$1”).
Q1.c: Après avoir écrit 0 dans le fichier passé en argument, votre script doit maintenant effectuer une boucle infinie. Dans cette boucle, votre script doit, chaque seconde, incrémenter la valeur contenu dans le fichier. Pour cela, votre script doit :
Lire le contenu du fichier passé en argument dans une variable n,
Additionner 1 à n avec la commande expr
,
Ecrire n dans le fichier passé en argument,
Attendre une seconde en invoquant la commande sleep 1.
Remarque : Une boucle infinie s’écrit de la façon suivante : while true ; do ... done
Pour arrêter un script qui ne se termine jamais comme celui que vous
mettez en œuvre à cette question, il suffit de saisir la combinaison de
touches Ctrl+C
.
Le but de cet exercice est d’écrire un petit programme capable de découper des lignes, c’est-à-dire d’extraire certains mots séparés par des blancs.
Q1: Pour commencer, nous allons vérifier que les arguments du script ne sont pas invalides. Les arguments sont invalides si :
Le nombre d’arguments n’est pas égal à 2,
Ou si le premier argument ne correspond pas à un fichier existant (test [ ! -e fic ]
),
Ou si le premier argument correspond à un répertoire (test [ -d fic]
).
Q2: Complétez votre script pour qu’il lise, ligne à ligne, le fichier passé en argument (premier argument) et écrive ces lignes sur la sortie standard. On vous rappelle que pour lire un fichier ligne à ligne à partir du fichier fichier, il faut utiliser la construction
while read line; do ... done <fichier>.
Q3: Modifiez votre script pour qu’il n’affiche que le nième mot de chaque ligne, où n est le second argument du script. Pour mettre en œuvre cet algorithme, nous vous conseillons d’utiliser une variable annexe, par exemple num, que vous initialiserez à zéro avant d’itérer sur les mots, et que vous incrémenterez à l’aide de la commande expr à chaque itération de la boucle sur les mots.
Le but de cet exercice est d’écrire un script qui prend en argument un fichier. Votre script doit lire les lignes provenant de l’entrée standard, et les écrire à la fois dans le fichier passé en argument et sur la sortie standard.
Q1 : Dans un premier temps, nous allons traiter le cas où les arguments du script sont invalides. Les arguments sont invalides si :
Il n’y a pas un et un seul argument ou
Si l’unique argument est un chemin vers un répertoire existant (le test correspondant est [ -d chemin ]
).
Écrivez un script nommé tee.sh qui renvoie faux (valeur 1) après avoir affiché un message d’erreur adéquat si les arguments sont invalides, et qui renvoie vrai (valeur 0) sinon.
Q2 : Modifiez votre script de façon:
A lire l’entrée standard ligne à ligne et
A afficher chaque ligne lue sur la sortie standard. On vous rappelle que pour lire l’entrée standard ligne à ligne, il faut utiliser la construction suivante :
while read line; do ...; done.
Remarque : Vous pouvez tester votre script de façon interactive en écrivant des lignes sur le
terminal. Dans ce cas, vous pouvez fermer le flux associé à l’entrée
standard avec la combinaison de touches Ctrl+D
. Vous pouvez aussi
tester votre script en redirigeant l’entrée standard à partir d’un
fichier, par exemple avec ./tee.sh fic < /etc/passwd
.
Q3: Nous allons maintenant dupliquer la sortie vers le fichier passé en argument.
Ouvrez un flux associé au fichier passé en argument au début du corps du
script. Vous devez ouvrir le flux en écriture et en écrasement.
(commande exec 3>fichier
). Modifiez aussi votre boucle de façon à
écrire chaque ligne lue à la fois sur la sortie standard (commande
echo ligne
) et dans le flux ouvert (commande echo ligne >&3
).
Objectifs :
Savoir observer les processus s’exécutant sur une machine
Manipuler un processus en cours d’exécution
Savoir tuer un (ensemble de) processus
Notions de concurrence
Q1: Trouvez, à l’aide de la commande ps, le PID du processus dhclient
.
Q2: Ajoutez l’option -l à la commande précédente et trouvez le nom du processus parent du processus dhclient.
Q3: Utilisez maintenant la commande pstree afin de trouver le PID du processus dhclient ainsi que le nom de son processus père.
Q4: Le répertoire /proc/$PID contient des fichiers décrivant l’état du processus <PID>
. Trouvez le fichier
contenant la ligne de commande ayant servi à lancer le processus
dhclient
.
Q5: Consultez la documentation de la commande yes
. À quoi sert-elle ?
Q6: Ouvrez un terminal et lancez la commande suivante :
$ yes "Unix, c'est super"
Pendant l’exécution de la commande yes
, lancez la commande top
dans
un autre terminal. Quels sont les processus qui consomment le plus de
CPU ?
Q7: Lancement en avant plan :
Tapez la commande emacs
pour lancer, en avant-plan, le processus
emacs
.
Pour quelle raison l’invite de commande (prompt) n’est plus présente
dans le terminal ? Tapez la commande ls
dans le terminal et
observez le résultat.
Dans le terminal, tapez Ctrl+Z
pour stopper le processus en
avant-plan (sans le détruire) et ainsi pouvoir utiliser le terminal.
Dans le terminal, vérifiez que vous pouvez exécuter la commande
ls
. Dans la fenêtre emacs, tapez quelques caractères pour vérifier
son fonctionnement.
Lancez la commande ps -l
pour identifier le processus qui
s’exécute et connaître l’état des autre processus lancés depuis ce
terminal. Consultez la documentation de la commande ps
pour
trouver cette information.
Tapez la commande fg
pour relancer le processus emacs
en
avant-plan. Pouvez vous maintenant écrire dans la fenêtre emacs ?
Dans le terminal ?
Q8: Lancement en arrière plan :
Si ce n’est pas déjà fait, tapez la commande «emacs» pour lancer, en
avant-plan, le processus emacs
.
Dans le terminal, tapez Ctrl+Z
afin de suspendre le processus
emacs. Vérifiez avec ps que le processus emacs
est bien suspendu.
Tapez la commande bg
pour relancer le processus emacs en
arrière-plan. Pouvez-vous maintenant écrire dans la fenêtre emacs ?
Dans le terminal ? Quelle est la différence entre les commandes fg
et bg
?
Utilisez la commande ps
pour afficher l’état du processus emacs
.
Fermez le programme emacs, puis relancez-le en terminant la ligne de
commande par “&” de la manière suivante : emacs &
. Vérifiez que le
terminal reste utilisable. Vérifier avec ps
que l’état du
processus emacs
est le même qu’à la question précédente.
Lors du lancement du processus emacs
à la question précédente, un
nombre est apparu sur le terminal. À quoi correspond-il ?
Remarque : Le caractère “&” terminant la ligne de commande pour lancer un processus en arrière plan n’a pas besoin d’être séparé d’un espace.
Q9: Tuer un processus :
Lancez 2 processus xeyes
en arrière-plan.
Retrouvez le PID du premier processus xeyes
lancé et utilisez la
commande kill
pour le détruire.
Vérifiez que le processus n’existe plus.
Q10: Tuer plusieurs processus :
Relancez un processus xeyes
en arrière-plan et vérifiez qu’il
existe bien 2 processus xeyes
.
Utilisez la commande killall
pour terminer tous les processus
xeyes
en cours d’exécution.
Cet exercice a pour but de vous apprendre à créer plusieurs processus en utilisant un algorithme récursif. Comme présenté sur la figure qui suit, vous allez créer une chaîne de N processus. Dans une chaîne de processus, chaque processus est le père du suivant, sauf pour le dernier processus de la chaîne, qui lui, n’a pas de fils. Le premier processus de la chaîne est appelé le processus initial et le dernier le processus final. Les autres processus sont appelés les processus intermédiaires.
L’algorithme que nous utilisons est récursif. Si N vaut 1, alors l’unique processus de la chaîne est le processus final. Il ne doit donc rien faire de spécial. Si N est strictement supérieur à 1, alors le processus doit encore créer N-1 processus. Pour cela, il lui suffit de lancer chaine.sh avec comme paramètre N-1.
Q11: Nous allons écrire le
script qui crée une chaîne de processus étape par étape. Pour commencer,
écrivez un script nommé chaine.sh
qui :
Vérifie ses arguments. Si le script ne reçoit pas un unique argument, il doit quitter avec un message d’erreur et un code de retour faux.
Affiche le message « Processus X démarre », où X est le PID du processus.
Quitte avec un code de retour vrai.
Q12: Avant de créer récursivement les processus, dans cette question, nous affichons ce que l’algorithme est supposé faire. Modifier votre script de façon à ce que :
Si N est strictement supérieur à 1, votre script affiche «
Il reste K processus à créer
», où K est égal à N-1,
Sinon, votre script affiche « Fin de chaîne
».
Q13: Nous finalisons maintenant l’algorithme. Après avoir affiché «
Il reste K processus à créer
», où K=N-1, votre script doit donc
lancer la commande ./chaine.sh K
pour créer le processus suivant de la
chaîne. Vérifiez que vous démarrez bien 4 processus lorsque vous
invoquez ./chaine.sh 4
.
Q14: Il faut maintenant lancer les processus
en tâche de fond. Un processus ne doit aussi se terminer que lorsque son
fils direct est terminé. Enfin, tous les processus (y compris le
processus final) doivent afficher Processus X termine
, où X est le PID
du processus courant. Pour les processus qui possèdent un fils, cet
affichage doit avoir lieu après avoir attendu la fin du fils.
Q15: Nous souhaitons maintenant communiquer le PID du processus initial jusqu’au processus final. Le langage bash fournit un mécanisme pour connaître le PID de son parent direct, mais pas de ses autres aïeuls. Il faut donc communiquer ce PID via un nouveau mécanisme. Pour mettre en œuvre un tel mécanisme, nous devons résoudre deux sous-problèmes:
Il faut être capable de communiquer le PID du processus initial
jusqu’au processus final. Pour cela, nous utilisons une variable
nommée pidInitial
, initialisée par le processus initial à son PID,
et exportée jusqu’au processus final.
Il faut que le processus initial soit capable de se reconnaître de
façon à initialiser et exporter cette variable pidInitial
. Tous
les processus, sauf le processus initial, vont démarrer avec une
copie de la variable pidInitial
puisque cette dernière est
exportée par le processus initial. Le processus initial est donc
l’unique processus pour lequel la variable pidInitial
n’est pas
définie initialement, c.-à-d., pour lequel la variable pidInitial
a pour valeur la chaîne de caractère vide, qui se reconnaît avec la
construction [ -z $pidInitial ]
.
Modifiez votre script de façon à ce que le processus initial initialise
et exporte la variable pidInitial
. Pour vous assurer que votre
programme est correct, modifiez aussi l’affichage «
Processus X démarre
», où X est le PID du processus courant, en «
Processus X démarre avec le processus initial Y
», où Y est le PID du
processus initial. Testez votre programme en lançant ./chaine.sh 4
.
Félicitation ! Vous venez d’écrire votre première application multi-processus complexe ! Mettez le script chaine.sh de côté, vous vous en servirez à la prochaine séance.
Un simple compteur
#! /bin/bash
if [ "$1" == "" ]; then
echo "Please provide exactly one argument" >&2 # &2 est la sortie d’erreur
echo "1"
exit 1 # un code de retour faux
fi
if [ ! -f "$1" ]; then
echo "$0: $1 is not a file name"
echo "1"
exit 1
fi
while true ; do
read v < "$1"
i=$(expr $v + 1)
echo "$i"
echo "$i" > "$1"
sleep 1
done
echo "0"
exit 0
Découper des lignes
#! /bin/bash
if [ $# -ne 2 ]; then
echo "Please provide exactly two arguments" >&2 # &2 est la sortie d’erreur
exit 1 # un code de retour faux
fi
if [ ! -e "$1" ]; then
echo "$0: $1 is not a file name" >&2
exit 1
fi
if [ -d "$1" ]; then
echo "$0: $1 can not be a directory" >&2
exit 1
fi
while read line; do
i=1
for word in $line
do
if [ "$i" -eq "$2" ]; then
echo $word
fi
i=$(expr $i + 1)
done
done < "$1"
exit 0
Duplication de flux
#! /bin/bash
if [ $# -ne 1 ]; then
echo "Please provide exactly one arguments" >&2 # &2 est la sortie d’erreur
exit 1 # un code de retour faux
fi
if [ -d "$1" ]; then
echo "$0: $1 can not be an existing directory" >&2
exit 1
fi
exec 3>"$1"
while read line; do
echo $line
echo $line >&3
done
exit 0
Chaine de processus
#! /bin/bash
if [ $# -ne 1 ]; then
echo "Please provide exactly one argument" >&2 # &2 est la sortie d’erreur
exit 1 # un code de retour faux
fi
if [ -z "$pidInitial" ]; then
echo Processus $$ demare
else
echo Processus $$ demare avec le processus initial $pidInitial
fi
export pidInitial=$$
if [ $1 -gt 1 ]; then
k=$(expr $1 - 1)
echo Il reste $k processus à crée
./chaine.sh $k
else
echo Fin de chaîne
fi
echo Processus $$ termine
exit 0