Consignes

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

L'archive contiendra :

Le fichier README n'est pas un compte-rendu "classique". Vous devez produire un document synthétique du style "fiche de révision" contenant un résumé des notions et concepts que vous aurez explorés pendant le TP.

L'objectif est que vous produisiez une synthèse contenant les choses qui peuvent vous servir, par exemple pour l'examen !

Il n'est pas nécessaire de connaitre tous les détails par cœur, mais les techniques utilisées pour les 2 premiers tiers du TP sont au programme de l'examen.

Ce fichier contiendra obligatoirement une entête avec :

Liens utiles

Préliminaires : script

Le shell (ou invite de commandes) permet d'interagir avec le système d'exploitation. Comme vous l'avez vu pendant le TP2, le shell agit comme un interpréteur : vous entrez une commande, le shell l'évalue, affiche le résultat et recommence. On parle de "read-eval-print loop" (REPL). (C'est le même principe que l'interpréteur Python que vous utilisez dans vos cours de programmation.)

Comme Python, le shell peut également lire des commandes dans un fichier texte, en général avec l'extension ".sh". Plutôt que de programme, on parle plutôt de script dans ce cas. Par exemple, GameShell (que vous avez utilisé au TP2) est essentiellement constitué d'un ensemble de scripts shell.

Dans ce TP, vous allez découvrir :

Bien sûr, tous les concepts vus au TP2 comme les redirections restent valides dans un script.

  1. Lancez un terminal, taper les commandes suivantes et observez le résultat

    $ pwd
          --> que se passe t'il ???
    $ cd
          --> que se passe t'il ???
    $ ls
          --> que se passe t'il ???
    $ cd /tmp
          --> que se passe t'il ???
    $ pwd
          --> que se passe t'il ???
  2. En utilisant un éditeur de texte, par exemple geany ou gedit, écrivez les même commandes dans un fichier appelé script1.sh dans votre répertoire de travail du TP :

    pwd
    cd
    ls
    cd /tmp
    pwd
    
  3. Vérifiez que le fichier apparait avec la commande ls

    $ ls
    README      script1.sh       ...

    (Si ce n'est pas le cas, utilisez cd pour vous déplacer dans le répertoire approprié.)

    La commande

    $ source script1.sh

    doit interpréter les lignes de votre fichier dans le shell courant.

    Lancez cette commande et comparer le résultat avec ce que vous avez observé au point 1.

  4. Les commentaires s'écrivent comme en Python : en les précédant d'un caractère #. Ajoutez un commentaire avec votre nom au début du fichier script1.sh et vérifiez qu'il produit le même résultat qu'auparavant.

Notez vos observations dans votre fichier README.

  • Si vous utilisez un autre shell que bash, il est possible que la commande source ne soit pas reconnue. Vous pouvez la remplacer par un point suivi d'un espace :

    $ . script1.sh
  • Ceci est pratique si vous avez besoin d'exécuter fréquemment une suite de commandes un peu longue : il suffit de les mettre dans un fichier. Bien entendu, si le fichier n'est pas dans le répertoire courant, il faudra donner le chemin complet après source.

    Si la suite de commandes est courte, il est également possible de les mettre sur une seule ligne en les séparant par des points virgules, dans le script, ou directement dans le shell :

    $ pwd; cd; ls; cd /tmp; pwd

1. Variables et affectation

Il est difficile de programmer sans stocker des valeurs dans des variables. Les variables du shell servent essentiellement à stocker des chaines de caractères ou des nombres entiers.

  1. L'affectation d'une valeur pour une variable se fait avec le signe = :

    prenom="Pierre"
    nom="Hyvernat"
    

    ATTENTION, il ne faut pas mettre d'espace autour du signe =.

  2. L'utilisation de la valeur d'une variable se fait en précédant le nom de la variable par le signe $. Par exemple,

    prenom="Pierre"
    nom="Hyvernat"
    echo "$prenom"
    

    affichera simplement Pierre. (La commande echo est un peu l'équivalent de la commande print de Python.)

N'oubliez pas les guillemets autour des chaines de caractères. Ils ne sont pas toujours obligatoire, mais leur absence peut entrainer des erreurs difficiles à repérer.

  1. Essayez de deviner ce qui sera affiché par le script suivant:

    prenom="Pierre"
    nom="Hyvernat"
    
    echo "Bienvenu $prenom"
    echo "Ton $nom est nom"
    

    Testez le script avec

    $ source script2.sh

    Notez attentivement ce qui se passe, et corrigez les erreurs présentes.

  2. Remplacez, dans les lignes echo ..., les guillemets double "..." par des guillemets simples '...' et relancez votre script. Comment interprêtez vous le résultat ?

Commentez et analisez les résultats des commandes suivantes dans votre fichier README :

$ nom="Hyvernat"
      --> que passe t'il
$ nom
      --> que passe t'il
$ $nom
      --> que passe t'il
$ echo "$nom"
      --> que passe t'il
  1. Certaines variables sont définies automatiquement par le shell. Affichez les et cherchez à quoi elles correspondent :

    • HOME

    • PWD

    • OLDPWD

    • SHELL

    • USER

  2. Testez l'effet de l'affectation suivante dans votre shell :

    $ PS1=""
          --> que se passe t'il

    Testez ensuite

    $ PS1="><> "

    Expliquez ce qui se passe dans votre fichier README.

arithmétique

  1. Testez le script suivant et analysez le résultat

    p=97
    q=73
    echo "La multiplication de $p par $q est égale à $p*$q."
    
  2. Par défaut, les variables numériques sont en fait considérées comme des chaines de caractères. Pour effectuer des opérations arithmétiques (+, -, *, /, %), il faut mettre l'expression voulue entre $(( et )).

    Modifiez le script pour qu'il affiche effectivement

    $ source script3.sh
    La multiplication de 97 par 73 est égale à 7081.

2. La boucle for

La boucle for du shell ressemble fortement à celle de Python. Sa syntaxe est

for VARIABLE in LISTE
do
  ...
done

Les points suivants sont importants :

  1. Testez le script suivant

    for N in "pomme" "poire" "pain au chocolat"
    do
        echo "> $N"
    done
    

    Que se passe t'il si vous oubliez les guillemets dans la liste for N in ... ?

  2. Remplacez la ligne for N in ... par for N in *. Lancez le script correspondant et expliquez son résultat.

  3. Modifiez le script pour qu'il affiche une liste numérotée des fichiers présents dans le répertoire courant.

    Voila par exemple ce qu'il pourrait afficher :

    $ source script4.sh
    1> README
    2> script1.sh
    3> script2.sh
    4> script3.sh
    5> script4.sh
    6> tmp

3. Conditionnelles

Comme tous les langages de programmation, le shell a une instruction if. Sa syntaxe est

if COMMANDE
then
    ...
elif
then
    ...
elif
then
    ...
else
    ...
fi

ATTENTION : n'oubliez ni le then ni le fi. Les elif et le else ne sont par contre pas obligatoire.

COMMANDE est une commande du shell dont la valeur de retour sera interprétée comme "vrai" ou "faux".

  1. Le script suivant contient une conditionnelle toute simple :

    if CONDITION
    then
      echo "C'est VRAI."
    else
      echo "C'est FAUX."
    fi
    

    Remplacez CONDITION par

    • 0 puis 1

    • false puis true

    et décrivez attentivement ce qui se passe.

  2. La valeur de retour d'une commande shell est stockée dans une variable spéciale ?. Sa valeur s'obtient donc avec $?.

    Affichez les valeurs de retour des commandes true et false.

    • Qu'est-ce qu'une valeur vraie pour le shell ?

    • Qu'est-ce qu'une valeur fausse pour le shell ?

    Décrivez ce que vous avez fait et les résultats dans votre fichier README.

  3. Regardez les valeurs de retours des commandes suivantes :

    $ ls
         ----> quelle valeur de retour ???
    $ ls /tuhsoeat
         ----> quelle valeur de retour ???

    À quoi peut servir la valeur de retour ?

  4. À votre avis, qu'affichera le script suivant ?

    if echo "false"
    then
      echo "vrai"
    else
      echo "faux"
    fi
    

    Testez et expliquez ce que vous observez.

La plupart des tests "intéressants" se font avec la commande test. Cette commande fait partie de la norme POSIX et est donc présente sur la plupart des systèmes.

Par exemple, pour tester que $i est strictement plus petit que 10, on peut utiliser la commande

$ test "$i" -lt 10

("lt" : "less than").

  1. Décrivez une manière de vérifier que test "$i" -lt 10 donne bien une valeur de retour "vraie" lorsque que la variable i est strictement plus petite que 10.

    Attention, si vous oubliez les guillemets et que la variable i n'existe pas (ou vaut la chaine vide), la commande test provoquera une erreur !

  2. La page de manuel de la commande test est disponible avec

    $ man test

    Cherchez le test permettant de vérifier si une chaine de caractère est vide, et ajoutez un test dans le script suivant

    prenom=""
    nom="Hyvernat"
    
    echo "Bienvenu $prenom"
    echo "Ton nom est $nom"
    

    pour que le premier affichage ne soit fait que lorsque la variable prenom est non vide.

  3. La commande test a un synonyme plus court, et souvent plus lisible : test UNE CONDITION est équivalent à [ UNE CONDITION ].

    • Vérifiez que la version courte fonctionne avec un test simple.

    • La commande test se trouve dans le répertoire /usr/bin. Vérifiez que ce répertoire contient également un fichier [.

ATTENTION, si vous utilisez la version courte de test avec les crochets, il ne faut pas oublier de mettre un espace après le crochet ouvrant "[" et avant le crochet fermant "]".

Les conditions complexes sont possibles en utilisant les options -a ("and") et -o ("or") de la commande test. Elles sont également possibles en utilisant les opérations && et || du shell :

Attention à ne pas confondre ces opérateurs avec & (pour lancer une commande en tache de fond) et | pour rediriger l'affichage d'une commande dans une autre.

Quelle différence y a t'il entre

Écrivez un script fichiers.sh qui parcourt le répertoire courant et

Voici un exemple d'exécution

$ source fichiers-correction.sh
VIDE:    fichier_vide.txt
FICHIER: fichiers-correction.sh
???:     lien_casse
FICHIER: script1.sh
FICHIER: script2.sh
FICHIER: script3.sh
FICHIER: script4.sh
FICHIER: script5.sh
FICHIER: script6.sh
REP:     tmp

Vous pouvez obtenir un répertoire, un fichier vide et un lien symbolique cassé avec

$ mkdir tmp
$ touch fichier_vide
$ ln -s /abcde lien_casse

4. Fonctions

Comme en Python, il est possible d'encapsuler du code dans des fonctions afin de pouvoir le réutiliser plus facilement. Ces fonctions peuvent être appelées dans le script, ou depuis le shell ayant chargé le script contenant la fonction.

Les fonctions sont définies avec

NOM_FONCTION() {
    ...
    ...
}

Contrairement à Python, on ne déclare ni ne donne de noms aux arguments de la fonction. La fonction définie pourra utiliser 1, 2 ou plus d'arguments. Elle pourra même n'en avoir aucun ! C'est donc au programmeur de gérer les arguments à la main. Pour cela, il dispose des variables suivantes :

La variable $@ est particulièrement utile pour faire une boucle sur les arguments de la fonction avec for x in "$@".

Pour appeler la fonction, il suffit d'écrire

$ NOM_FONCTION ARG1 ARG2 ...

Attention, il n'y a pas de parenthèses ou de virgule comme en Python.

Écrivez une fonction show_args avec le comportement suivant :

Voici un exemple d'exécution :

$ source script_show_args.sh
$ show_args Il fait chaud et beau.
> 5 argument(s)
    argument 1: Il
    argument 2: fait
    argument 3: chaud
    argument 4: et
    argument 5: beau.
$ show_args pomme poire "pain au chocolat"
> 3 argument(s)
    argument 1: pomme
    argument 2: poire
    argument 3: pain au chocolat

Si les guillemets ne sont pas pris en compte (dans le second appel), vérifiez votre boucle : il faut faire for x in "$@" plutôt que for x in $@.

5. Un système de favoris pour le shell

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

Nous allons créer un système de "favoris" pour bash, avec 4 fonctions :

Ces 4 fonctions seront définies dans un fichier favoris.sh.

Voici un exemple d'utilisation

$ source favoris.sh
$ pwd
/home/hyvernat/info201/TP/sujets/TP4
$ A info201-4
  Le répertoire /home/hyvernat/info201/TP/sujets/TP4 est sauvegardé dans vos favoris.
  raccourci : info201-4
$ cd
$ pwd
/home/hyvernat/
$ C info201-4
  Vous êtes maintenant dans le répertoire /home/hyvernat/info201/TP/sujets/TP4.
$ pwd
/home/hyvernat/info201/TP/sujets/TP4
$ X info201-4
  Le favoris "info201-4" est supprimé de votre liste.
$ cd
$ C info201-4
  Le favoris "info201-4" n'existe pas.

5.2. Précisions

Chacune des 4 fonctions demandées ne fait que quelques lignes. N'hésitez pas à demander à votre encadrant si vous pensez que vous faites des choses trop complexes.

A: ajout de favoris

Vous devrez utiliser un fichier (caché) .favoris_bash qui sera créé dans votre dossier personnel. Ce fichier contiendra une ligne par favori. Chaque ligne contiendra

Créez un fichier favoris.sh pour contenir les fonctions que vous allez écrire.

  1. N'oubliez pas de mettre quelques commentaires avec au minimum vos noms, groupe et filière.

  2. Définissez une variable shell FAV qui prendra la valeur $HOME/.favoris_bash. Il s'agira du fichier qui contiendra vos favoris...

  3. Définissez une fonction A qui permettra de sauver un nouveau favori en affichant un petit message.

    Les points importants sont :

    • cette fonction utilisera son premier argument $1 comme nom du favori,

    • le nom du répertoire courant est contenu dans la variable $PWD,

    • on peut ajouter une ligne dans le fichier des favoris (variable $FAV) avec la commande echo et une redirection >>.

    • vous utiliserez la chaine -> pour séparer le nom du favori de son chemin d'accès.

  4. Testez votre fonction. Par exemple :

    $ source favoris.sh
    $ A info201-3
      Le répertoire /home/hyvernat/info201/TP/sujets/TP3 est sauvegardé dans vos favoris.
      raccourci : info201-3
    $ cd ~/Téléchargements
    $ A tel
      Le répertoire /home/hyvernat/Téléchargements est sauvegardé dans vos favoris.
      raccourci : tel
    $ cat ~/.favoris_bash
    info201-3 -> /home/hyvernat/info201/TP/sujets/TP4
    tel -> /home/hyvernat/Téléchargements

L : lister les favoris

Ajoutez maintenant la fonction L qui permet d'afficher la liste des favoris sauvegardés. Pour cela, il suffit d'afficher le contenu du fichier $FAV...

Pour rendre l'affichage plus joli, vous pouvez utiliser la commande column -t qui aligne le contenu d'un fichier par colonnes.

Remarque : cette fonction n'utilise aucun argument !

X : suppression d'un favori

Ajoutez maintenant la fonction X qui permet de supprimer le favori donné en argument ($1).

Pour cela, le plus simple est d'utiliser la commande

grep -vw "^MOT" FICHIER

qui affiche toutes les lignes de FICHIER, sauf celles qui commencent par MOT. (Attention, le ^ est nécessaire.)

En redirigeant le résultat de cette commande dans un fichier (avec >), vous pouvez supprimer un favori...

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.

C : "cd" dans un favori

La fonction la plus importante est C qui permet de se déplacer dans un répertoire sauvegardé dans un favori.

L'objectif est de récupérer le chemin d'accès associé au favori donné en argument (variable $1) et de le sauvegarder dans une variable CHEMIN, puis de d'effectuer la commande

cd $CHEMIN

On peut récupérer ce chemin avec une commande complexe :

CHEMIN=$(COMMANDE)

La commande en question pourra par exemple utiliser

Cette manière de procéder permet d'éviter de faire une boucle sur le fichier de favoris !

  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.

5.3. Pour aller plus loin

Essayer d'améliorer votre script, par exemple avec les fonctionnalités comme :