MO101 - Introduction à Linux - Feuille TD4
Bienvenue dans le quatirème TD du cours MO101 !
Au cours de ce TD, nous allons appronfondir nos connaissances des commandes de traitements de chaines de caractères sed et des scripts Bash.
Partie -1 - Production de documents
Commencez par récupérer l'archive pour ce TD, et désarchivez la dans le répertoire mo101
Q1) Donnez le code LaTeX associé au document PDF 'integration.pdf'
Voici quelques indications qui vous aideront à faire cet exercice :
- La documentation du paquet "amsmath" qui donne accès à différents environnements pour écrire des équations est accessible là (en anglais)
- Pour ajouter un résumé à un document il faut utiliser l'environnement "\begin{abstract}...\end{abstract}"
- A noter que le changement de fontes dans les modes mathématiques nécessite l'utilisation de commandes spécifiques qui sont
- \mathbf{} pour mettre en gras
- \mathcal{} pour une écriture caligraphique
- \mathtt{} pour une écriture "machine à écrire"
- \mathit{} pour une écriture en italique
- Faire référence à une équation demande deux choses :
- ajouter un \label{eq:nom-label} dans l'environnement 'equation'
- utiliser la commande \eqref{eq:nom-label} dans le texte pour faire référence à cette équation. La commande \eqref{} permet d'avoir des parenthèses autour du numéro d'équation dans le texte.
Cf le cours pour les différentes constructions LaTeX utilisées pour générer ce document
Partie 0 - Préliminaires
Récupération des archives pour ce TD en ligne de commande
Q1) Depuis votre répertoire personnel, placez vous dans le répertoire mo101
Réponse: cd mo101
Q2) Téléchargez l'archive avec la commande wget
Réponse: wget https://perso.ensta-paris.fr/~chapoutot/teaching/mo101/archive_td4.tar.gz Pour récupérer l'adresse de l'archive, il faut faire clique droit sur le lien ci-dessus et faire copier le lien.
Q3.b) À partir de votre dossier mo101, désarchivez l'archive du TD4 à la ligne de commande.
Réponse : tar -xzvf archive_td4.tar.gz
Vous devriez maintenant avoir son contenu dans un dossier "td4". Rendez-vous dans ce sous-dossier, et répondez aux prochaines questions.
Partie 1 - Arborescence de fichiers (révision des manipulations simples)
Q1. Allez dans le répertoire "arborescence".
Réponse : cd arborescence
Q2. Créez les répertoires permettant d'avoir l'arborescence suivante
arborescence
|-- rep1/
|-- srep1/
|-- srep2/
|-- rep2/
|-- srep1/
|-- srep2/
|-- rep3/
|-- srep1/
|-- srep2/
Réponse : mkdir -p rep1/srep1 rep1/srep2 mkdir -p rep2/srep1 rep2/srep2 mkdir -p rep3/srep1 rep3/srep2 on peut aussi faire un script Bash si on veut
Q3. Allez dans le répertoire rep3/srep2.
Réponse : cd rep3/srep2
Q4. Créez un fichier nommé "message.txt" dont le contenu est la chaine de caractères "for you eyes only".
Réponse : echo "for your eyes only" > message.txt
Q5. Créez une archive nommée "secret.tar.gz" contenant le fichier nouvellement crée.
Réponse : tar -cvzf secret.tar.gz message.txt
Q6. Copiez cette archive dans le répeteroire rep1/srep1
Réponse : cp secret.tar.gz ../../rep1/srep1/secret.tar.gz
Q7. Supprimez le fichier message.txt du répertoire courant
Réponse : rm message.txt
Q8. Allez dans le répertoire rep1/srep1
Réponse : cd ../../rep1/srep1
Q9. Désarchivez l'archive secret.tar.gz
Réponse : tar -xvzf secret.tar.gz
Q10. Retournez dans le répertoire td4
Réponse : cd ../../..
Q11. Supprimez le repertoire "arborescence"
Réponse : rm -rf arborescence
Partie 1 - Faire part de naissance
L'objectif de cet exercice est de générer automatiquement des faire-part de naissance à partir de modèles pré établis et une liste de naissances.
Les modèles sont dans le répertoire modeles et la liste des naissances est dans le fichier naissances.txt. Les modèles de faire-part de naissance sont formés de texte dans lequel des balises spéciales ont été insérées, par exemple, <PRENOM>
ou <DATE>
, qui représentent les emplacements dans le texte où devront se situer le prénom et la date de naissance du bébé.
Dans le fichier naissances.txt, il y a une liste de naissance regroupant les informations nécessaires psur générer les faire-part.
Q1. Examinez le contenu et la structure du fichier naissances.txt
Réponse : less naissances.txt C'est un fichier au format CSV (Comma separated values). Le contenu de chaque colonne est dans l'ordre: le genre, le prénom, la taille, le poids et la date de naissance du bébé.
Q2. Créez un répertoire faire-part qui contiendra les faire-part générés.
Réponse : mkdir faire-part
Q3. Ecrire un script creerFairePart.sh qui prend comme paramètre un nom de modèle de faire-part, le prenom, la taille, le poids et la date de naissance du bébé et remplace dans le modèle du faire part les informations nécessaires. Il affichera le résultat sur la sortie standard
Indice: on pourra utiliser la commande sed et sa capacité à substituer un texte par un autre.
Exemple: Un exemple d'exécution de ce script est
$ cat modeles/faire-part-fille-3.txt
Coucou c’est moi !
Je m'appelle <PRENOM>
Et de mes <TAILLE>cm et <POIDS>kg
Je ravis déjà mes parents !
Ils m’ont découverte le <DATE>
Et depuis sont chaque jour enchantés !
$
$ bash creerFairePart.sh modeles/faire-part-fille-3.txt Louise 45 3.3 2019-1-02
Coucou c’est moi !
Je m'appelle Louise
Et de mes 45cm et 3.3kg
Je ravis déjà mes parents !
Ils m’ont découverte le 2019-1-02
Et depuis sont chaque jour enchantés !
Réponse : #! /bin/bash MODELE=$1 PRENOM=$2 TAILLE=$3 POIDS=$4 DATE=$5 sed -e "s/<PRENOM>/$PRENOM/" -e "s/<TAILLE>/$TAILLE/" -e "s/<POIDS>/$POIDS/" -e "s/<DATE>/$DATE/" $MODELE Note: les double guillements dans les actions de la commande sed sont importants pour permettre l'expansion des variables.
Q4. Les dates dans le fichier naissances.txt sont sous le formats anglo-saxon (année-mois-jour) et avec un chiffre pour le mois. Modifiez votre script précédent pour transformer la date au format européen et avec un nom de mois au lieu d'un chiffre. Indice: man date
Réponse : La commande à utiliser est date -d ma_date "+%d %B %Y" d'où la modification suivante dans le script #! /bin/bash MODELE=$1 PRENOM=$2 TAILLE=$3 POIDS=$4 DATE=`date -d $5 "+%d %B %Y"` sed -e "s/<PRENOM>/$PRENOM/" -e "s/<TAILLE>/$TAILLE/" -e "s/<POIDS>/$POIDS/" -e "s/<DATE>/$DATE/" $MODELE Note: les double guillements dans les actions de la commande sed sont toujours importants pour permettre l'expansion des variables. Note: 2 sous Mac Os la commande date ne fonctionne pas de la même manière que sous Linux (la solution donnée ici ne fonctionne pas)
Q5. Ecrire une commande qui permet de calculer le nombre de modèles de faire-part pour les filles.
Réponse : ls modeles/*fille* | wc -l
Q6. Ecrire une commande qui permet de calculer le nombre de modèles de faire-part pour les garçons.
Réponse : ls modeles/*garcon* | wc -l
Q7. Donnez un script choisirModeleFille.sh qui permet de choisir alétoirement un modèle de faire part pour les filles. L'adresse du fichier du modèle choisi sera affiché sur la sortie standard.
Note : pour tirer un nombre aléatoire entre 1 et 10, il faut utiliser la commande $(( ( RANDOM % 10 ) + 1 ))
.
Réponse : #! /bin/bash NBMODELE=`ls modeles/*fille* | wc -l` CHOIX=$(( (RANDOM % $NBMODELE) + 1)) echo `ls modeles/*fille-$CHOIX*`
Q8. Donnez un script choisirModeleGarcon.sh qui permet de choisir alétoirement un modèle de faire part pour les garçons. L'adresse du fichier du modèle choisi sera affiché sur la sortie standard.
Note : pour tirer un nombre aléatoire entre 1 et 10, il faut utiliser la commande $(( ( RANDOM % 10 ) + 1 ))
.
Réponse : #! /bin/bash NBMODELE=`ls modeles/*garcon* | wc -l` CHOIX=$(( (RANDOM % $NBMODELE) + 1)) echo `ls modeles/*garcon-$CHOIX*`
Q9. A l'aide de la commande cut, écrivez une commande qui permet de récupérer le genre d'un bébé dans une ligne du fichier naissances.txt
Réponse : echo "Garcon;Hugo;50;3;2018-04-27" | cut -d";" -f 1
Q10. Créez un répetoire faire-part.
Réponse : mkdir faire-part
Q11. Ecrire un script genererFairePart.sh qui génère la liste des faire-part en lisant ligne par ligne le fichier naissances.txt et en utilisant les scirpts précédents. Chaque faire-part générer sera sauvegarder dans un fichier dont le nom suivra le format prenom-date.txt et placer dans le répertoir faire-part.
La construction en shell pour lire un fichier ligne par ligne est
cat fichier | while read LIGNE ; do
...
echo $LIGNE
...
done
Réponse : #! /bin/bash cat naissances.txt | while read LINE; do GENRE=`echo $LINE | cut -d";" -f 1` PRENOM=`echo $LINE | cut -d";" -f 2` TAILLE=`echo $LINE | cut -d";" -f 3` POIDS=`echo $LINE | cut -d";" -f 4` DATE=`echo $LINE | cut -d";" -f 5` if [ $GENRE == 'Fille' ]; then bash creerFairePart.sh `bash choisirModeleFille.sh` $PRENOM $TAILLE $POIDS $DATE > faire-part/$PRENOM-$DATE.txt else bash creerFairePart.sh `bash choisirModeleGarcon.sh` $PRENOM $TAILLE $POIDS $DATE > faire-part/$PRENOM-$DATE.txt fi done
Q12. Faire une archive nommée faire-part.tar.gz de tous les faire-part générés.
Réponse : tar -cvzf faire-part.tar.gz faire-part
Partie 2 - La commande sed
La commande sed (pour Stream Editor) permet de transformer un flux de caractères en entrée pour le transformer en substituant du texte, en supprimant du texte ou encore en extrayant du texte.
On rappelle la forme générale des commandes de sed
Une expressions régulière ou expression rationnelle est une chaine de caractères qui décrit un ensemble de chaines de caractères. Elle utilise une syntaxe particulière pour décrire différentes classes de caractères qui composent l'ensemble de chaines de caractères. Il existe plusieurs types de langage d'expressions régulières, nous allons ici considérer un sous ensemble des BRE (Basic Regular Expression). La commande sed (Stream Editor) est capable d'utiliser des expressions rationnelles pour faire des traitements sur les chaines de caractères (cf "man sed").
Une expression rationnelle (BRE) est composée :
- de caractères ordinaires
- de méta-caractères : ., *, ^, $, |, \(\), [], [^], \1, \2, ..., \9 ?
Exemples :
- "foo" : reconnait toutes les chaines de caractères contenant foo
- "ˆfoo" : toute chaîne de caractères qui commencent par foo
- "foo$" : toute chaîne de caractères qui finit par foo
- "ˆfoo$" : seul le mot foo est reconnu, rien ne peut le précéder ni le suivre dans la chaîne de caractères
- "f.o" : ‘.’ désigne n’importe quel caractère, toute chaîne de caractéres qui contient f puis un caractère puis o
- "f[mnopq]o" : tous les caractères de la liste mnopq entre crochets autorisés à cet emplacement
- "f[m-p]o" : [m-p] tous les caractères de l’intervalle de caractères entre m et p
- "f[ˆa-lR-W0-9]o" : [ˆa-lR-W0-9] reconnaît tous les caractères autres que ceux de la liste de séquences indiquées
- "a*" : reconnaît 0 fois ou plus de a
A noter que l'opérateur * est gourmand, c'est-à-dire qu'il essaiera toujours de reconnaitre la chaine de caractères la plus longue.
La principale différence entre les ERE et les BRE réside dans la notion de groupe capturant dans les BRE dénoté par \(\) les parenthèses. Il permet de mettre en mémoire une chaine de caractères qui correspond à une expression rationnelle entourée des parenthèses. On peut ensuite utiliser la chaine en mémoire pour autre chose. Les groupes capturant sont notés à partir de la parenthèse ouvrante et sont au nombre de 9 au maximum. Utiliser le premier groupe capturant est possible grâce à la commande \1, le second avec la commande \2, etc.
Q1. A l'aide de la commande sed, supprimez la première ligne du fichier naissances.txt.
Réponse : sed -e '1d' naissances.txt Note: nous utilisons comme adresse le numéro de ligne du fichier.
Q2. A l'aide de la commande sed, supprimez la première ligne du fichier naissances.txt en faisant une copie de sauvegarde à l'aide de l'option -i.
Réponse : sed -i.save -e '1d' naissances.txt Note: nous utilisons comme adresse le numéro de ligne du fichier. L'option -i permet de faire une modification en place (i.e., change le contenu du fichier __naissances.txt__) et fait une copie de sauvegarde dont le nom est __naissances.txt.save__
Q3. A l'aide de la commande sed, supprimez toutes les lignes du fichier naissances.txt qui contiennent le prénom Nathan
Réponse : sed -e '/Nathan/d' naissances.txt Note: nous utilisons comme adresse l'expression rationnelle /Nathan/ donc sed détecte les lignes correspondant à cette expression rationnelle avant d'appliquer le traitement, ici une suppression.
Q4. A l'aide de la commande sed, affichez les lignes 15 à 28 du fichier naissances.txt.
Réponse : sed -n -e '18,25p' naissances.txt Note: nous utilisons un "range" d'addresses décrites par les lignes du fichier. L'option -n permet de ne pas afficher les lignes (comportement par défaut) du fichier afin de n'afficher que les lignes sélectionnées
Q5. A l'aide de la commande sed, supprimez les lignes 5 à 10 du fichiers naissances.txt.
Réponse : sed -e '5,10d' naissances.txt Note: nous utilisons un "range" d'addresses décrites par les lignes du fichier. Nous n'utilisons pas ici l'option -n afin que les lignes non supprimées du fichier soient affichées.
Q6. A l'aide de la commande sed, extraire la balise titre de la page HTML index.html
Réponse : sed -n -e '/<title>/,/<\/title>/p' index.html Note: Nous utilisons un "range" d'addresses décrites par expressions rationnelles. L'option -n permet de ne pas afficher les lignes (comportement par défaut) du fichier afin de n'afficher que les lignes sélectionnées. Note 2: Quand un "range" d'adresses utilisent des expressions rationnelles, la seconde expressions rationnelles n'est évaluée qu'après la fin de la première ligne qui correspond à la première expression rationnelle. En particulier si les balises `` et ` ` étaient sur la même ligne cela ne fonctionnerait pas.
Q7. A l'aide de la commande sed, extraire toutes les balises li de la page HTML index.html
Réponse : sed -n -e '/<li>/p' index.html Note: Comme les balises `<li>` et `</li>` sont sur la même ligne, nous ne pouvons pas utiliser la solution précédente. Nous décrivons donc une adresse avec une seule expression rationnelle afin d'afficher les lignes qui nous intéressent.
Q8. Que font les commandes suivantes ?
sed -e 's/<\(.*\)>/<\U\1>/' index.html
sed -e 's/<\(.*\)>/<\u\1>/' index.html
Réponse : L'utilisation des parenthèses \(\) permet de définir un bloc capturant, c'est-à-dire de garder en mémoire la chaîne de caractères qui correspond à l'expression rationnelle entre parenthèse. On peut ensuite utiliser cette chaine de caractères capturés, nommée ici \1, pour faire un traitement. En particulier, la fonction \U permet de mettre en lettres majuscules la chaine de caractères qui suit immédiatement cette fonction. La fonction \u permet de mettre seulement le premier caractère en lettre majuscule de la chaine de caractères qui la suit. Dans ces exemples, les deux commandes permettent soit de mettre tous les noms des balises HTML en lettres majuscules soit uniquement la première lettre des noms des balises en majuscule. A noter, qu'il existe également la fonction \L et \l qui permettent de mettre en lettres minuscules la chaine de caractères ou la première lettre de la chaine de caractères qui suit la fonction. Note: les fonctions \U et \u ne fonctionnent pas sous Mac OS.
Q9. En vous aidant de la question 5.; mettez en majuscule le titre (définit entre les balises <h1>
et </h1>
) de la page HTML.
Réponse : sed -e 's/<h1>\([^<]*\)/<h1>\U\1/' index.html Note: L'expression rationnelle [^<]* permet de capturer des chaines de caractères formées de tous les caractères sauf le caractères <. On l'utilise ici pour capturer la chaine entre les balises <h1> et surtout pour éviter de capturer la balise fermante </h1>.
Partie 3 - Test d'applications (Facultatif)
Dans cette partie nous allons écrire un script Bash qui permet de tester un programme. La fonction considérée est la fonction nth qui prend en argument une chaîne de caractères et une position et affiche le caractère se trouvant à cette position dans la chaîne.
Le script Python3 nth_print.py met en oeuvre cette fonction nth avec un ajout pour prendre les arguments de la fonction sur la ligne de commande.
Par exemple, dans le terminal on aura l'exécution suivante :
$ python3 nth_print.py "salut" 3
u
Dans le fichier dataset.txt, un jeu de données de test a été défini. L'objectif de cet exercice est d'exécuter les différents test défini dans le fichier dataset.txt pour vérifier que le programme nth_print.py fonctionne correctement.
Q1. Examinez le contenu et la structure du fichier dataset.txt
Réponse : less dataset.txt C'est un fichier au format CSV (Comma separated values). Le contenu de chaque colonne est dans l'ordre: une chaine de caractère, une position et le résultat attendu de l'exécution du script **nth_print.py**
Q2. Ecrire un script executeTest.sh qui prend en argument une chaine de caractère, une position et un résultat attendu. Ce script exécute le programme nth_print.py et compare la sortie produite par ce programme au résultat attendu.
En exemple:
$ bash executerTest.sh chaine 3 i
Test nth("chaine", 3) => i == i (OK)
$ bash executerTest.sh chaine 3 j
Test nth("chaine", 3) => i != j (KO)
Réponse : #! /bin/bash CHAINE=$1 POSITION=$2 ATTENDU=$3 RESULTAT=`python3 nth_print.py "$1" $2` echo -n "Test nth(\"$CHAINE\", $POSITION) => $RESULTAT" if [ "$ATTENDU" == "$RESULTAT" ]; then echo " == $ATTENDU (OK)" else echo " != $ATTENDU (KO)" fi Note: dans l'appel au programme **nth_print.py** il faut protéger le premier argument avec des guillemets car potentiellement cette chaîne peut contenir des espaces. Note: Dans la condition du **if** il faut mettre les variables entre guillemets car potentiellement leur valeur peut contenir des espaces.
Q3. Ecrire un script toutTester.sh qui exécute tous les tests définis dans le fichier dataset.txt et affiche le résultat sur la sortie standard.
On rappelle que la construction en Shell pour lire un fichier ligne par ligne est
cat fichier | while read LIGNE ; do
...
echo $LIGNE
...
done
Réponse : #! /bin/bash cat dataset.txt | while read LIGNE; do CHAINE=`echo $LIGNE | cut -d"," -f 1` POSITION=`echo $LIGNE | cut -d"," -f 2` ATTENDU=`echo $LIGNE | cut -d"," -f 3` bash executerTest.sh "$CHAINE" $POSITION "$ATTENDU" done Note: Dans l'appel au script executerTest.sh on protège les arguments par des guillemets car potentiellement ceux-ci ont des valeurs qui peuvent contenir des espaces.
Q4. Est-ce que l'exécution des tests vous semble normal ?
Réponse : Il y a un problème avec le troisième test avec la position 0.
Q5. Corrigez le programme nth_print.py
Réponse : il faut mettre une comparaison strict dans le première conditionnelle qui détecte si on donne une position négative.
Q6. Ajoutez des compteurs permettant de calculer le nombre de tests réussis et échoués afin de faire un rapport en fin d'exécution du script toutTester.sh
Réponse : Une première solution qui ne fonctionne pas (Pourquoi ?) #! /bin/bash SUCCES=0 ECHECS=0 cat dataset.txt | while read LIGNE; do CHAINE=`echo $LIGNE | cut -d"," -f 1` POSITION=`echo $LIGNE | cut -d"," -f 2` ATTENDU=`echo $LIGNE | cut -d"," -f 3` RESULTAT=`bash executerTest.sh "$CHAINE" $POSITION "$ATTENDU"` echo $RESULTAT if [ `echo "$RESULTAT" | grep -c '(OK)$'` -eq 1 ]; then SUCCES=$((SUCCES + 1)) echo "SUCCES $SUCCES" else ECHECS=$((ECHECS + 1)) echo "Echecs $ECHECS" fi done echo "$SUCCES test(s) reussi(s) et $ECHECS test(s) echoue(s)" Car l'utilisation d'un pipe (cat dataset | while ...) crée un sous-processus qui ne permet pas de partager les variables SUCCES et ECHEC. La solution est d'utiliser un autre structure de lecture de fichier ligne par ligne qui utilise une redirection du flux d'entrée de la forme while read LINE; do echo -e "$LINE\n" done < file.txt En conséquence nous avons la solution suivante #! /bin/bash SUCCES=0 ECHECS=0 while read LIGNE; do CHAINE=`echo $LIGNE | cut -d"," -f 1` POSITION=`echo $LIGNE | cut -d"," -f 2` ATTENDU=`echo $LIGNE | cut -d"," -f 3` RESULTAT=`bash executerTest.sh "$CHAINE" $POSITION "$ATTENDU"` echo $RESULTAT if [ `echo "$RESULTAT" | grep -c '(OK)$'` -eq 1 ]; then SUCCES=$((SUCCES + 1)) echo "SUCCES $SUCCES" else ECHECS=$((ECHECS + 1)) echo "Echecs $ECHECS" fi done < dataset.txt echo "$SUCCES test(s) reussi(s) et $ECHECS test(s) echoue(s)"