Table des matières


Objectifs :

L’objectif central de ce TP va être de créer une fonction pour afficher notre arborescence de fichier par récursion en bash.

Une fonction récursive est une fonction qui s’appelle elle même pour opérer son traitement, on parle alors d’appels récursifs de fonction.

Dans la première partie, on fera un exercice introductif à la récursion avant de se lancer dans la création du script bash pour faire notre arbre. On croisera les flux sur notre route.

Un second objectif va être de comprendre les notions de sections critiques et de mutex et de réussir à les mettre en pratique.

Ave Caesar


Nous allons créer une fonction arroser permettant d’incrémenter une valeur dans un fichier plante.

Version impérative

Q1 : Dans un fichier arrosoir.sh, créez une fonction arroser(). Pour le moment, cette fonction doit afficher “Je m’amuse tellement à faire du bash” dans le terminal (ne pas tricher en omettant le ’ dans la chaîne).

Q2 : Exécutez cette fonction dans le script.

Q3 : Créer un fichier nommé plante grâce à une commande. Ce fichier contient uniquement le chiffre 0 sur sa première ligne.

Q4 : Modifiez la fonction arroser afin qu’elle affiche la première ligne du fichier plante dans le terminal.

Q5 : Modifiez la fonction arroser pour que l’appel du script prenne un argument x en paramètre et écrive la valeur de ce paramètre à la place du 0 du fichier plante.

Q6 : Remettez 0 dans le fichier plante. Créez une fonction boucleImp qui va lire puis incrémenter autant de fois la valeur contenue dans le fichier plante que la valeur de x via une boucle for ou while. La fonction arroser appelle maintenant la fonction boucleImp.

Normalement, à ce stade, vous pouvez faire ./arrosoir.sh 15 pour voir quinze fois la valeur de plante être augmentée de un.

Version récursive

Q7 : Créez une fonction boucleRec qui suit le comportement suivant (et où x est le paramètre donné lors de l’exécution de la fonction) :

Si X n’est pas nul :

- On enlève 1 à X

- On appelle la fonction boucleRec
Sinon :

- On affiche "X n’est plus"

Q8 : Modifiez boucleRec pour faire en sorte qu’elle incrémente en plus la premier ligne du fichier plante. Le script arrosoir.sh utilise maintenant boucleRec au lieu de boucleImp.

Normalement, vous avez à ce stade un script au comportement analogue à celui de Q6 mais avec un fonctionnement récursif (i.e qui s’appelle soit même pour mener à terme son traitement).

Auprès de mon arbre


L’objectif de cet exercice est de créer un script permettant d’afficher l’arborescence de fichiers à partir du répertoire courant. Toute explication demandée doit être rendue dans un fichier texte explication.txt.

Q9 : Créez l’arborescence de fichier suivante (Rx sont des répertoires et Fx sont des fichiers) avec le minimum de commandes possibles :

image

Explicitez ce que vous avez tapé dans le terminal dans explication.txt.

Q10 : Créez un script treeRec permettant de savoir si l’élément pris en paramètre est un fichier ou un répertoire puis affiche son nom. Il faut que l’exécution du script se termine en renvoyant un statut de succès.
Expliquez comment retourner un statut d’échec dans explications.txt.

NT : "-d chemin" dans un if permet de déterminer si le chemin pointe vers un répertoire.

Q11 : Élargissez votre script précédent pour qu’il traite l’ensemble des éléments du répertoire courant au lieu d’un seul élément.

Pour cela, on utilise une fonction bash qui permet de lister les éléments d’un répertoire dont on stocke le résultat dans une variable. Il faut ensuite parcourir la liste contenue dans cette variable à l’aide d’une boucle pour afficher le résultat sous la forme suivante :

-- Fichier foo.js  
-- Fichier bar.php  
-- Répertoire Barfoo  
-- Fichier foubar.tlp
...

Q12 : Faites une fonction parseRec qui contient le code déjà effectué dans la question précédente. Révisez treeRec afin qu’il utilise la fonction parseRec.

Q13 : Révisez la fonction parseRec afin que lorsque l’élément traité est un répertoire, un appel récursif est effectué à l’intérieur de ce répertoire. Pour ce faire, le script rentre dans le répertoire, appelle la fonction parseRec, puis ressort du répertoire. On affiche ainsi la liste complète des fichiers et des répertories de l’arborescence. En exécutant votre script dans le répertoire R1 de l’arborescence créée précédemment, vous devriez avoir le résultat suivant :

---- R2
---- F1
---- F2
---- F3
---- R3
---- R4
---- F4
---- F5

À ce stade, il n’est plus nécessaire d’afficher si l’élément traité est un répertoire ou un fichier, vous pouvez supprimer cette information si vous le souhaitez.

Q14 : Créez une fonction deepness qui prend un paramètre x et qui affiche la chaîne “---- ” x fois.

NT : La commande echo fait automatiquement un retour à la ligne. Vous pouvez utiliser la commande printf qui permet d’éviter ce problème si nécessaire. Pour forcer un retour à la ligne avec printf, ajoutez \n à la fin de la chaîne.

Q15 : Mettre à jour l’affichage pour indiquer la profondeur de l’élément observé dans l’arborescence de fichier. Pour ce faire, on utilise la fonction deepness dans la fonction parseRec. Vous devriez avoir le résultat suivant :

---- R2
---- ---- F1
---- ---- F2
---- F3
---- ---- R3
---- ---- ---- R4
---- ---- ---- ---- F4
---- ---- ---- ---- F5

Q16 : Modifiez votre script afin qu’il puisse s’exécuter ailleurs que dans le répertoire courant. Si le script a un chemin en paramètre, il s’exécute à cet emplacement. Sinon, il s’exécute dans le répertoire courant.

Q17 : En supposant que le script fonctionne, si celui-ci s’arrête abruptement (ex : ctrl-c, bug, etc…), où serez vous positionné dans l’arborescence? Pourquoi? Expliquez dans le fichier explications.txt.

Mise en évidence des incohérences provoquées par les commutations


Soit le script ecriture.sh suivant :

#! /bin/bash

if [ $# -lt 1 ] ; then
  echo "Il faut au moins un parametre"
  exit 1
fi
for elem in "$@" ; do
  if [ ! -e "$elem" ] ; then
    echo premier $$ > "$elem"
  else
    echo suivant $$ >> "$elem"
  fi
done

Q1 : Exécutez deux fois de suite la commande ./ecriture.sh a b c et expliquez le contenu des fichiers.

Q2 : Soit le script lancement_ecriture.sh suivant qui permet d’exécuter en concurrence deux processus ecriture.sh.

 #! /bin/bash

 rm -f f1 f2 f3

 ./ecriture.sh f1 f2 f3 & ./ecriture.sh f1 f2 f3

 wait

Exécutez ce script jusqu’à ce que le contenu d’un des fichiers f1, f2 ou f3 ne contienne qu’une seule ligne au lieu de deux, c’est-à-dire, jusqu’à ce qu’une des écritures soit perdue. Expliquez le résultat obtenu.

Q3 : Identifiez la section critique dans ecriture.sh.

Q4 : Modifiez le script ecriture.sh de façon à assurer une exclusion mutuelle sur la section critique.

Q5 : Reprenez le code de la question précédente, mais en faisant attention à ne pas bloquer les processus s’ils n’écrivent pas dans le même fichier.

Corrections


arrosoir.sh

#! /bin/bash

function boucleRec() {
  x=$1
  if [ "$x" -ne 0 ]; then
    # increment value in file
    line=$(head -n 1 plante)
    line=$((line+1))
    echo $line > plante

    # call recursively
    x=$((x-1))
    boucleRec $x
  else 
    echo "X n’est plus"
  fi
}

function boucleImp() {
  for i in `seq 1 $1`;
  do
    line=$(head -n 1 plante)
    line=$((line+1))
    echo $line > plante
  done
}

function arroser() {  
  boucleImp $1
}

#arroser $1
boucleRec $1

treeRec.sh

#! /bin/bash

function deepness {
  for i in `seq 1 $1`;
  do
    printf "%s " "----"
  done
  printf "\n"
}

function parseRec {
  d=$(deepness $2)
  if [ -d $1 ]; then
    
    # do not print root folder
    if [ $2 -gt 0 ]; then
      echo $d $1
    fi

    # recurse for children
    s=$(ls $1)
    cd $1
    for f in $s; do
      parseRec $f $(($2+1))
    done
    cd ..
  elif [ -f $1 ]; then
    echo $d $1
  else 
    echo $1 is invalid
    exit 1
  fi
}

p=$1
if [ -z $p ]; then
  p="."
fi

parseRec $p 0

exit 0

lancement_ecriture.sh

 #! /bin/bash

 rm -f f1 f2 f3

 ./ecriture.sh f1 f2 f3 & ./ecriture.sh f1 f2 f3

wait

ecriture.sh

#! /bin/bash

if [ $# -lt 1 ] ; then
  echo "Il faut au moins un parametre"
  exit 1
fi
for elem in "$@" ; do

  # lock
  ./P.sh "$elem".lock

  if [ ! -e "$elem" ] ; then
    echo premier $$ > "$elem"
  else
    echo suivant $$ >> "$elem"
  fi

  # un-lock
  ./V.sh "$elem".lock

done