Table des matières


Ce TP comme ceux à venir devront être effectués sur un système UNIX. Ces deux parties sont estimée à 1h30 chacune.

Flux


Un simple compteur

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 :

É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 :

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.

Découper des lignes

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 :

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.

Duplication de flux

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 :

É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:

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).

Processus


Objectifs :

Observer un processus

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 ?

Suspendre un processus

Q7: Lancement en avant plan :

  1. Tapez la commande emacs pour lancer, en avant-plan, le processus emacs.

  2. 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.

  3. Dans le terminal, tapez Ctrl+Z pour stopper le processus en avant-plan (sans le détruire) et ainsi pouvoir utiliser le terminal.

  4. 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.

  5. 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.

  6. 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 :

  1. Si ce n’est pas déjà fait, tapez la commande «emacs» pour lancer, en avant-plan, le processus emacs.

  2. Dans le terminal, tapez Ctrl+Z afin de suspendre le processus emacs. Vérifiez avec ps que le processus emacs est bien suspendu.

  3. 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 ?

  4. Utilisez la commande ps pour afficher l’état du processus emacs.

  5. 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.

  6. 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.

Tuer un processus

Q9: Tuer un processus :

  1. Lancez 2 processus xeyes en arrière-plan.

  2. Retrouvez le PID du premier processus xeyes lancé et utilisez la commande kill pour le détruire.

  3. Vérifiez que le processus n’existe plus.

Q10: Tuer plusieurs processus :

  1. Relancez un processus xeyes en arrière-plan et vérifiez qu’il existe bien 2 processus xeyes.

  2. Utilisez la commande killall pour terminer tous les processus xeyes en cours d’exécution.

Chaîne de processus

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.

image

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 :

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 :

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:

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.

Corrections


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