Consignes

Ce sujet de TP comporte quelques "mini projets" qui utilisent certaines des notions vues en cours, TD ou TP. L'objectif est de réaliser 1 ou 2 (ou même plus) de ces mini projets.

Avant de commencer un des projet, il est impératif de faire la section Préliminaires.

Le rendu du TP consistera en une archive (fichier .zip, .tar, etc.) comportant votre travail.

Attention, les fichiers README seront pris en compte pour l'évaluation du TP...

Liens utiles

Objectifs du TP

L'objectif de ce TP est de réaliser, en "autonomie", des scripts utilisant les notions utilisés dans les TP précédents. Les mini projets proposés ont été choisis soit pour illustrer spécifiquement des points importants, soit parce qu'ils correspondent à des vrais besoins.

(répondre à cette question juste avant d'envoyer votre dernière soumissions !)

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

  2. Donnez deux exemples (ou plus) de taches qui pourraient être "facilement" scriptables. Vous pouvez vous inspirer des projets proposés dans le sujet, ou en inventer de nouvelles.

Préliminaires

  1. Lisez les sections fonctions shell et boucle sur les arguments d'une fonction et arguments individuels d'une fonction.

  2. Téléchargez le fichier mon_premier_script.sh, lisez le et testez le.

  3. En vous inspirant de la fonction affiche_fichiers, écrivez une fonction types qui, pour chacun de ses argument, affiche son type :
    • fichier,
    • dossier,
    • inconnu.

  4. N'oubliez pas de tester...

    Par Exemple

    $ source mon_premier_script.sh
    $ types * uhesantuhensa
    Bureau : dossier
    info202 : dossier
    lettre.txt : fichier
    tmp : dossier
    uhesantuhensa : inconnu
    

1. Mini projet 1 : système de favoris pour le shell

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

1.2. Objectif

L'objectif de ce mini projet est de créer un petit système de "favoris" pour vos répertoires importants.

Votre script devra fournir 4 fonctions :

Exemple d'utilisation

$ source favoris.sh
$ pwd
/home/hyvernat/info202/TP/sujets/TP5
$ S info202-5
Le répertoire /home/hyvernat/info202/TP/sujets/TP5 est sauvegardé dans vos favoris.
  -> raccourci : info202-5
$ cd
$ pwd
/home/hyvernat/
$ C info202-5
Vous êtes maintenant dans le répertoire /home/hyvernat/info202/TP/sujets/TP5.
$ pwd
/home/hyvernat/info202/TP/sujets/TP5
$ R info202-5
Le favoris "info202-5" est supprimé de votre liste.
$ cd
$ C info202-5
Le favoris "info202-5" n'existe pas.

1.3. Précisions

Stockage des favoris

Votre script utilisera un fichier (caché) .favoris_bash qui sera créé dans votre dossier personnel. Ce fichier contiendra une ligne par favori. Chaque ligne contiendra

Pour simplifier la gestion de ce fichier, il est conseillé de le définir dans une variable au début de votre script:

FAV=$HOME/.favoris_bash

Note : la variable HOME contient le chemin absolu vers votre dossier personnel...

Manipulation du fichier

Pour manipuler le fichiers de favoris, il faudra utiliser les choses suivantes.

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.

Pour faire ceci, il faut donc rediriger la commande dans un autre fichier temporaire, et remplacer ensuite le fichier d'origine par ce fichier temporaire.

1.4. Pour aller plus loin

2. Mini projet 2 : gérer une "TODO list"

2.1. Description

Pour éviter d'oublier des choses importantes à faire, je voudrais garder une une liste dans mon ordinateur. J'aimerais pouvoir ajouter des tâches, les lister, et en supprimer.

Remarque: je pourrais également utiliser cette fonctionnalité pour garder :

2.2. Objectif

Le script doit fournir une unique fonction todo qui offre 3 fonctionnalités :

Voici un exemple d'utilisation :

$ source todo.sh
$ todo list
1 - finir TP d'info202
2 - téléphoner à tata
3 - inviter Edith à manger
4 - passer la serpillère dans l'entrée
$ todo done 1
La tâche 1 (finir TP d'info202) est faite !
$ todo list
1 - téléphoner à tata
2 - inviter Edith à manger
3 - passer la serpillère dans l'entrée
$ todo add 2 réviser la chimie
La tâche "réviser la chimie" a été ajoutée en position 2.
$ todo list
1 - téléphoner à tata
2 - réviser la chimie
3 - inviter Edith à manger
4 - passer la serpillère dans l'entrée

2.3. Précisions

Votre fonction devra analyser son premier argument afin de décider quelle opération effectuer. Il faudra donc se reporter à la section arguments individuels d'une fonction et la description des conditionnelles.

Stockage des tâches

La liste des tâche doit être sauvegardée dans un fichier (caché) .todo_list qui sera stocké dans votre dossier personnel. Ce fichier contiendra une ligne par tâche, sans numéro.

Pour simplifier la gestion de ce fichier, il est conseillé de le définir dans une variable au début de votre script:

TACHES=$HOME/.todo_list

Note : la variable HOME contient le chemin absolu vers votre dossier personnel...

Manipulation du fichier

Pour manipuler les tâches, il faudra utiliser les choses suivantes.

2.4. Pour aller plus loin

3. Mini projet 3 : statistiques sur des fichiers

3.1. Description

J'aimerais bien avoir des statistiques sur mes fichiers :

3.2. Objectif

Le script devra fournir une fonction qui analyse le contenu du répertoire courant et de ses sous-répertoires et afficher des statistiques. La quantité de détails dépendra de la valeur de l'argument donné à la fonction :

Voici un exemple d'exécution

$ pwd
/home/hyvernat/info202/
$ statistiques
Analyse de /home/hyvernat/info202/ :
  - 45 répertoires
  - 114 fichiers
  - taille totale : 18M

$ statistiques 2
Analyse de /home/hyvernat/info202/ :
  - 45 répertoires
      - 1 répertoire caché
      - 0 répertoire vide
  - 114 fichiers dont
      - 3 fichiers cachés
      - 1 fichier vide
  - taille totale : 18M

$ statistiques 3
Analyse de /home/hyvernat/info202/ :
  - 45 répertoires
      - 1 répertoire caché
      - 0 répertoire vide
  - 114 fichiers dont
      - 3 fichiers cachés
      - 1 fichier vide
      - 87 fichiers de moins de 512kio
      - 0 fichier de plus de 15Mio
      - le plus gros fichier est
               /home/hyvernat/info202/CM1/Img/RAM_old-plane_4k.jpg
    Il y a :
      - 5 fichiers Python
      - 17 fichiers image
      - 0 fichier vidéo
  - taille totale : 18M

3.3. Précisions

L'ingrédient principal de cette fonction sera la commande find et des redirections CMD1 | CMD2. Vous pouvez vous reporter aux sections pertinentes dans la liste des commandes du shell.

Il n'est pas nécessaire d'écrire des boucles pour cette fonction : chaque information peut être calculée directement avec une commande shell. Par exemple, voila une manière simple de compter le nombre de répertoires vides :

$ find -type d -empty | wc -l

Pour le reste, reportez vous aux sections suivantes avant de commencer :

Pour vérifier la valeur de l'argument, il faudra se référer à :

3.4. Pour aller plus loin

4. Mini projet 4 : génération d'une petite galerie d'image (HTML)

4.1. Description

J'ai quelques photos que je souhaite partager facilement sur ma page Internet. J'aimerais générer une petite page HTML qui affiche des petites vignettes. Un clic sur une de ces vignette devra afficher la photo originale...

4.2. Objectif

La fonction devra prendre une liste de fichiers images en arguments et

Voici un exemple d'exécution :

$ source ./galerie.sh
$ galerie *.???
inclusion de l'image Gerbil1.jpg
inclusion de l'image Gerbil2.png
inclusion de l'image Jaculus_orientalis.jpg
inclusion de l'image Jerboa.png
inclusion de l'image Meriones_unguiculatus.jpg
inclusion de l'image Springharelg.jpg

et vous pouvez voir le résultat ici. (Pour voir le contenu du fichier, vous pouvez faire afficher le code source de la page...)

4.3. Précisions

Vous pouvez utiliser les photos de cette archive (prises sur Wikipedia) pour tester votre fonction.

Il va falloir faire des boucles sur les arguments d'une fonction.

  1. Pour générer une vignette, on peut utiliser la commande

    $ convert FICHIER -resize 128x128 VIGNETTE
    
    (Si cette commande n'existe pas, il faut installer le paquet imagemagick.)

  2. Pour ajouter des lignes dans un fichier, vous pourrez utiliser la commande echo avec une redirection >>.

  3. Le schéma minimale pour une page web valide est

    <!DOCTYPE html>
    <html>
    <head>
    <title>TITRE</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    </head>
    <body>
       CODE HTML
    </body>
    </html>
    
    TITRE est le titre de la page et CODE HTML est le contenu HTML de la page.
  4. Le contenu de la page doit ressembler à

    <ul>
    <li><a href="IMAGE1"><img src="VIGNETTE1"></a></li>
    <li><a href="IMAGE2"><img src="VIGNETTE2"></a></li>
    <li><a href="IMAGE3"><img src="VIGNETTE3"></a></li>
    <li><a href="IMAGE4"><img src="VIGNETTE4"></a></li>
    </ul>
    

4.4. Pour aller plus loin

  1. Générer un tableau à deux dimensions plutôt qu'une liste.
  2. Ajouter un peu de style (css ou autre) pour rendre la page plus jolie.
  3. Laisser le choix de la taille des vignettes à l'utilisateur.
  4. etc.

5. Mini projet 5 : renommer des fichiers photos

5.1. Description

Les photos prises par mon téléphone ont des noms pas très jolis : IMG_20160312_124305306.jpg, etc.. Pour les archiver, j'aimerais les renommer en WeekEnd_2016-02-12_16-35-12.jpg, WeekEnd_2016-02-12_16-36-24.jpg, etc.

5.2. Objectif

Dans l'exemple précédent, c'est l'utilisateur qui choisit explicitement le préfixe WeekEnd. La date et l'heure sont automatiquement ajoutées au nom de fichier.

Voici un exemple d'utilisation :

$ source ./renomme.sh
$ ls *.jpg
IMG_123456789.jpg  IMG_928332498.jpg  IMG_987654321.jpg
$ renomme Lac *.jpg
IMG_123456789.jpg  ==>  Lac_2015-08-30_16-52-52.jpg
IMG_928332498.jpg  ==>  Lac_2015-09-04_16-34-07.jpg
IMG_987654321.jpg  ==>  Lac_2015-08-30_16-53-27.jpg
$ ls *.jpg
Lac_2015-08-30_16-52-52.jpg  Lac_2015-08-30_16-53-27.jpg  Lac_2015-09-04_16-34-07.jpg

5.3. Précisions

Vous pouvez utiliser les photos de cette archive (prises sur Wikipedia) pour tester votre fonction.

Reportez vous aux sections

Vous aurez besoin de récupérer l'extension d'un fichier contenu dans une variable. Pour ceci, l'incantation magique suivante convient : si la variable s'appelle img, on peut faire

    ext=${img##*.}

Attention, dans le shell, le symbole _ peut faire partie du nom d'une variable. Pour cette raisson, $img_$prefix ne sera pas interprété comme "la variable $img suivie d'un _ suivi de la variable $prefix", mais comme "la variable $img_ (qui n'existe probablement pas) suivie de la variable $prefix". La solution est de mettre le nom de la variable entre accolades : ${img}_$prefix.

5.4. Pour aller plus loin

6. Mini projet 6 : modifier la date de création de fichiers photos

6.1. Description

La galerie photo de mon téléphone Android affiche les photos par date de modification des fichiers. Lors de la restauration d'une sauvegarde, les fichiers sont recréés et les photos sont donc affichées dans le désordre.

Comme la date de prise de la photo se trouve dans le fichier (données "EXIF"), j'aimerais écrire un script pour remettre de l'ordre dans tout ça.

6.2. Objectif

Il s'agit d'écrire une fonction shell qui prend une liste de fichiers en arguments et change la date de modification de chacun de ces fichiers en fonction des informations EXIF (Exchangeable image file format).

Si ces informations ne sont pas présentes, les méta-données des fichier ne sont pas modifiées.

Voici un exemple d'exécution :

$ source maj_dates.sh
$ ls -tr *.jpg
Belledonne_soleil.jpg  Belledonne.jpg  Bauges_Mont_Blanc.jpg  Bauges_Belledonnes.jpg  Bauges.jpg
$ maj_dates *.jpg
Modification du fichier Bauges.jpg
Modification du fichier Bauges_Belledonnes.jpg
Modification du fichier Bauges_Mont_Blanc.jpg
Modification du fichier Belledonne.jpg
Modification du fichier Belledonne_soleil.jpg
Fin de la fonction
$ ls -tr *.jpg
Belledonne.jpg  Bauges.jpg  Bauges_Belledonnes.jpg  Bauges_Mont_Blanc.jpg  Belledonne_soleil.jpg

Note, ls -tr permet d'afficher la liste des fichiers dans l'ordre chronologique de dernière modification.

6.3. Précisions

Vous pouvez utiliser les photos de cette archive (prises sur Wikipedia) pour tester votre fonction.

Commencez par lire les sections suivantes :

6.4. Pour aller plus loin

Compléments sur le shell

Fonctions shell

Un script shell est un petit programme écrit dans le langage du shell. C'est un fichier contenant des commandes et des constructions similaires à celles trouvées dans les langages de programmation plus "évolués".

La première ligne d'un tel fichier doit être

#!/bin/bash

afin que le système le reconnaisse comme script shell.

On peut définir une fonction dans un script bash de la manière suivante :

function test() {
    CMD1
    CMD2
    CMD3
    ...
}

Cela permet d'ajouter une commande : une fois que le fichier est lu par le shell, par exemple par

$ source FICHIER

la commande test effectuera les commandes CMD1, CMD2, etc.

Comme avec Python et Idle, le fait d'avoir écrit une fonction n'est pas suffisant pour pouvoir l'exécuter. Il faut "charger" le fichier contenant la définition des fonctions avant de pouvoir les utiliser.

Avec Idle, il fallait choisir le menu "Run", "Run module" (ou le raccourci clavier "F5").

Dans le shell, on peut charger un fichier avec la commande

  $ source FICHIER

D'autres fonctionnalités intéressantes sont :

instruction echo
On peut faire un affichage simple avec la commande echo

echo "FIN de la fonction "
variables
On peut définir des variables du shell avec le signe =:

var="..."
Attention, il ne faut pas mettre d'espace autour du signe =.

Pour utiliser la valeur d'une variable, il faut précéder son nom du signe $

echo "La valeur de var est $var"
Pour initialiser une variable avec le résultat d'une autre commande, il faut utiliser

var=$(CMD)
CMD est la commande à exécuter.

conditionnelles
Pour faire des instructions conditionnelles, on peut utiliser un if. La syntaxe est

if [ TEST ]
then
    ...
elif [ TEST ]
then
    ...
elif [ TEST ]
then
    ...
else
    ...
fi
Attention, les espaces sont obligatoires après le symbole [ et avant le symbole ] !

TEST est une condition, qui porte en général sur des variables :
  • -z "$var" pour tester si la variable est vide ou non,
  • -f "$var" pour tester si la variable contient un nom de fichier qui existe,
  • "$var" == "hello" pour tester si la variable contient la chaine hello,
  • etc.

La liste des tests possibles est accessible avec

$ man test

Boucle sur les arguments d'une fonction

Les boucles du langage bash ressemblent à

for i in LIST
do
    ...
done

LIST est une liste de chaines, séparées par des espaces. Par exemple

$ for i in chat chien souris
> do
>     echo "animal : $i"
> done
animal : chat
animal : chien
animal : souris

Les arguments d'une fonction sont automatiquement mis dans une variable spéciale appelée $@. Si le fichier script.sh contient

#!/bin/bash

function animaux() {
  for a in "$@"
  do
    echo "animal : $a"
  done
  echo "FIN de la fonction"
}

alors l'exécution donne

$ source script.sh
$ animaux chat chien chauve souris canard
animal : chat
animal : chien
animal : chauve
animal : souris
animal : canard
FIN de la fonction
$ animaux
FIN de la fonction

Arguments individuels d'une fonction

Comme expliqué dans la section boucle sur les arguments d'une fonction, la liste des arguments d'une fonction est appelée $@.

Pour accéder aux premiers arguments individuellement, il faut utiliser les variables $1, $2, ... $9.

Pour accéder aux arguments suivants (après le numéro 9), il faut "décaler" les arguments. La commande shift supprime le premier argument et décale les suivants. Ainsi, après un shift, la variable $1 contient l'argument numéro 2, etc.

Par exemple, si le fichier args.sh contient

#!/bin/bash

function montre_args() {
  echo "Tous les arguments : $@"
  echo "Argument 1: $1"
  shift
  echo "Autres arguments : $@"
}

on aura

$ source ./args.sh
$ montre_args ananas pomme poire kiwi
Tous les arguments : ananas pomme poire kiwi
Argument 1: ananas
Autres arguments : pomme poire kiwi

Opérations arithmétiques en bash

Les calcul arithmétique en bash doivent obligatoirement se trouver dans un $((...)).

$ echo "1 + 2"
1 + 2
$ echo "$((1 + 2))"
3

On peut utiliser des variables, et stocker le résultat dans une variable:

$ V=117
$ echo "la valeur de V est $V"
la valeur de V est 117
$ V=$(( $V / 2 ))
$ echo "la valeur de V est $V"
la valeur de V est 58

Attention le résultat d'une opération arithmétique n'est pas une commande :

$ $((1 + 2))
bash: 3 : commande introuvable

Quelques commandes utiles

Commandes head et tail

Les commandes head et tail permette de récupérer des lignes au début ou à la fin d'un fichier :

Commande touch

La commande touch permet de modifier la date de dernier accès et date de dernière modification d'un fichier :

$ ls -l examen.pdf
-rw-r--r-- 1 hyvernat hyvernat 114087 2017-04-10 09:58:14 examen.pdf
$ touch -t 200902132331.30  examen.pdf
-rw-r--r-- 1 hyvernat hyvernat 114087 2009-02-13 23:31:30 examen.pdf
$ touch examen.pdf
-rw-r--r-- 1 hyvernat hyvernat 114087 2017-04-24 09:38:44 examen.pdf

La date donnée à touch a la forme SSAAMMJJhhmm.ss, où SS(siècle), AA (année) et .ss (seconde) sont facultatifs.

Commandes cut et tr

La commande tr permet de modifier certains caractères, ou d'en supprimer. Cette commande agit sur l'entrée standard :

La commande cut permet de récupérer seulement certaines parties d'une chaine : on précise un délimiteur, les numéros des champs qui nous intéressent. Cette commande agit sur l'entrée standard :

Commande file

La commande file essaie de deviner le type des fichiers donnés en argument :

$ file test.py log tmp TP4 Lac.jpg
test.py: Python script, ASCII text executable
log:     ASCII text
tmp:     empty
TP4:     directory
Lac.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72,
segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=14],
baseline, precision 8, 320x480, frames 3

Il est possible d'afficher un type de fichier plus succinct : le type MIME en donnant l'option -i à la commande :

$ file -i test.py log tmp TP4 Lac.jpg
test.py: text/x-python; charset=us-ascii
log:     text/plain; charset=us-ascii
tmp:     inode/x-empty; charset=binary
TP4:     inode/directory; charset=binary
Lac.jpg: image/jpeg; charset=binary

Il n'est parfois pas nécessaire d'afficher le nom du fichier. Cela se fait en ajoutant l'option -b :

$ file -bi test.py log tmp TP4 Lac.jpg
text/x-python; charset=us-ascii
text/plain; charset=us-ascii
inode/x-empty; charset=binary
inode/directory; charset=binary
image/jpeg; charset=binary

La commande du

La commande du (disk usage) prend une liste de fichiers en argument et affiche la taille occupée (en Kio) par chaque fichier. L'option -c permet d'afficher également l'espace total utilisé par les fichiers et l'option -h permet d'afficher un taille en utilisant des unités "raisonnables" (k, M, G).

Par exemple :

$ du -ch *.html *.pdf
12K     Fabrication d'un micro-processeur.html
56K     cours.html
2.2M    2I010_2016_support.pdf
12K     survie.pdf
12K     t.pdf
2.3M    total

Attention, sans aucun argument, la commande du calcule la taille occupée par tous les fichiers contenu dans le répertoire courant et ses sous-répertoires.

Informations EXIF

Les données EXIF peuvent contenir de nombreuses informations renseignées par l'appareil photo :

exiftool est un utilitaire qui permet de récupérer ces informations

$ exiftool Bauges.jpg
...
Create Date                     : 2011:08:11 12:41:02.00
Shutter Speed Value             : 1/200
...
Flash                           : No Flash
Focal Length                    : 32.0 mm
...

Il est possible de récupérer un unique champs de la manière suivante :

$ exiftool -CreateDate Bauges.jpg
Create Date                     : 2011:08:11 12:41:02.00

L'option -b est utile dans les scripts car elle permet de récupérer seulement la valeur du champs :

$ exiftool -b -CreateDate Bauges.jpg
2011:08:11 12:41:02.00$

Si la commande exiftool n'existe pas sur votre système, vous pouvez :