Il existe de nombreux shells: sh, zsh, ksh, bash, csh, tcsh, etc. Leur syntaxe varie beaucoup. Les corrections de ce TP seront données pour le shell bash uniquement. Il est assez répandu (c'est le shell par défaut de GNU/Linux et MacOS, entre autres). Vous pouvez, bien sûr, faire le TP avec n'importe quel shell.

L'élève curieux pourra également se reporter au TP analogue proposé en 2007 par Antoine Miné.

Aide

Vous aurez probablement besoin des commandes Unix suivantes:

  • man(1) pour comprendre l'utilisation de ces commandes! Le numéro entre parenthèse indique la section du manuel (option -s) où est documentée la commande (utile en cas d'homonymes). man man pour bien commencer.
  • echo(1) pour écrire un message à l'écran ou dans un fichier (redirection avec > et >>),
  • test(1) pour tester le type d'un fichier ou comparer des valeurs,
  • sed(1) pour effectuer des substitutions de chaînes de caractères (commande s/.../.../g en particulier),
  • grep(1) pour filtrer les lignes d'un fichier,
  • sort(1) pour ordonner les lignes d'un fichier et éventuellement supprimer les doublons (option -u),
  • tar(1) pour décompresser des archives .tar, .tar.gz, .tgz.
  • md5sum(1) pour calculer et vérifier la valeur du haché MD5 d'un fichier

Vous pouvez également vous reporter à la documentation de bash, à bash(1) et au cours.

On rappelle enfin qu'un script shell est un fichier texte toto.sh qui commence par une ligne précisant le nom de l'interpréteur, par exemple:

#!/bin/bash

(ou tout autre shell de votre choix) et qui est rendu exécutable par la commande chmod +x toto.sh. Il suffit alors de taper ./toto.sh pour l'exécuter. top

Exercice 1 - Affichage

  1. Écrire le programme Hello World! en shell.

    #!/bin/bash
    echo "Hello world!"



  2. Écrire un script shell qui affiche les nombres premiers entre 2 et un entier n passé en argument.
    (Crible d'Erathosthène)

    #!/bin/bash
    
    if [ "$#" = "0" -o "$1" = "-h" ]; then
        echo Usage : $(basename $0) n
        echo Affiche les premiers entre 2 et n.
        exit 1
    fi
    
    EntierMax=$1                  
    let FinCrible=EntierMax/2         
    
    TableauPremiers=('' '' $(seq 2 $EntierMax))
    # Initialise un tableau d'entiers avec TableauPremiers[i]=i pour i>1
    
    i=1
    until (( ( i += 1 ) > FinCrible ))  
    do
      if [[ -n $TableauPremiers[i] ]]
      then
        t=$i
        until (( ( t += i ) > EntierMax ))
        do
          TableauPremiers[t]=''
        done
      fi  
    done  
    echo ${TableauPremiers[*]}
    
    exit 0 
    



  3. Lorsqu'une commande prend en argument un chemin est que celui-ci n'existe pas, un message d'erreur est affiché, par exemple :

    % ls /usr/lib/thissoft/config/Makefile
    /usr/lib/thissoft/config/Makefile: No such file or directory.

    Écrire un script shell verif qui affichera la partie gauche du chemin qui est valide et la première entrée inexistante :

    % verif /usr/lib/thissoft/config/Makefile
    /usr/lib/thissoft: No such directory.

    #!/bin/bash
    
    if [ "$#" = "0" -o "$1" = "-h" ]; then
        echo "Usage : $(basename $0) "
        echo "Affiche le prefixe valide de "
        exit 1
    fi
    
    pos=`echo "$1/" | grep -o "/" | wc -l`
    # Compte le nombre de "/" dans $1 
    # (en en ajoutant un à la fin pour l'utilisateur etourdi)
    
    cpt=1
    
    while [ "$cpt" -le "$pos" ] 
    do
      dir=`echo $1 | cut -d / -f0-$cpt`
      if [ -d "$dir" ] ; then 
          let "cpt += 1"
      else 
         break
      fi
    done
    
    
    if [ "$cpt" -eq "1" ] 
        then
        echo "pas de chemin valide"
    else
        cpt=$((cpt-1))
        dir=`echo $1 | cut -d / -f0-$cpt`
        echo "$dir: repertoire valide"
    fi
    
    exit 0
    



  4. Écrire un script shell permettant d'afficher l'arborescence d'un répertoire.

    #!/bin/bash
    
    exploration () {
        for dir in *
          do
          if [ -d "$dir" ] ; then 
    	  niveau=0 
    	  while [ $niveau != $prof ] # pour aligner
    	    do
                echo -n "|   "  
                let "niveau += 1" 
    	  done
    	  echo "+---$dir" 
    	  if cd "$dir" ; then 
    	      let "prof += 1"
    	      exploration  
    	  fi
          fi
        done
       cd ..  
       if [ "$prof" ] ; then  
           estfini=1 
       fi
       let "prof -= 1"
    }
    
    ######################################
    
    if [ $# = 0 ] ; then
        cd `pwd` # repertoire courant si non specifie
    else
        cd $1    # sinon premier argument
    fi
    
    estfini=0 ; prof=0 ; niveau=0
    
    while [ "$estfini" != 1 ] 
      do
      exploration 
    done
    
    exit 0
    

  5. top

Exercice 2 - Gestion des fichiers

  1. Écrire un script shell qui renomme les fichiers dont le nom contient une ou plusieurs espaces
    (en remplaçant les espaces par _ par exemple).

    #! /bin/bash
    
    if [ $# = 0 ] ; then
        cd `pwd` # repertoire courant si non specifie
    else
        cd $1    # sinon premier argument
    fi
    
    
    for fichier in *
    do
         echo "$fichier" | grep -q " "  # -q : grep n'écrit rien
         if [ $? -eq 0 ]                
         then
           n=`echo $fichier | sed -e "s/ /_/g"` 
           mv "$fichier" "$n"                  
         fi
    done   
    
    exit 0
    



  2. Écrire un script shell qui produit la liste de tous les fichiers de taille supérieure à n Mo (où n est un entier passé en argument).

    #!/bin/bash
    if [ "$#" = "0" -o "$1" = "-h" ]; then
        echo "Usage : $(basename $0)  n"
        echo "Recherche fichiers de plus de n Mo dans ."
        exit 1
    fi
    
    find $1 -type f -size +$2M # version simple
    
    # ou en plus joli :
    # find $1 -type f -size +$2M -exec ls -lh {} \; | \
    #  awk '{ print $8 ": " $5 }'
    



  3. Écrire un script shell qui recherche les fichiers en double dans un répertoire donné.

    #!/bin/bash
    if [ "$#" = "0" -o "$1" = "-h" ]; then
        echo "Usage : $(basename $0) "
        echo "Recherche les doublons dans ".
        exit 1
    fi
    
    find $1 ! -empty -type f -printf "%s '%p'\n" | \
    # cherche tous les fichiers du repertoire et affiche taille/nom
    sort -n | \
    # tri par taille
    cut -d" " -f2- | \
    # enleve la taille
    xargs md5sum | \
    # calcule l'empreinte MD5
    sort | \
    # tri suivante l'empreinte MD5
    uniq -w32 -d --all-repeated=separate | \
    # affiche les lignes consecutives dont les 
    # 32 premiers caractères sont identiques
    cut -c35-
    # affiche le resultat à partir du caractère 35
    



  4. Écrire un script shell qui crée une archive .tag.gz de tous les fichiers modifiés pendant les dernières 24 heures.

    #!/bin/bash
    
    cd $HOME;
    quand=$(date "+%y-%m-%d")
    cible=archive.${quand};
    
    tar -zcvf "$HOME/$cible.tar.gz" \
    `find . -mtime -1 -type f -print`
    



  5. Écrire un script jeter qui permet de manipuler une poubelle à fichier - un répertoire - nommé poubelle et situé à votre racine. La commande accepte trois options:
    1. jeter -l pour lister le contenu de la poubelle ;
    2. jeter -t pour donner la taille de la poubelle ;
    3. jeter -s fichier chemin pour sortir le fichier fichier de la poubelle et le placer à l'emplacement chemin ;
    4. jeter -v pour vider la poubelle ;
    5. jeter fichier1 fichier2 ... pour déplacer les fichiers considérés vers la poubelle ;
    Si la poubelle n'existe pas, elle est créée à l'appel de la commande.

    #!/bin/bash
    
    
    if [ "$#" = "0" -o "$1" = "-h" ]; then
        echo Usage : $(basename $0) [-l|-s|-t|-v] [fichiers]
        exit 1
    fi
    
    
    #! /bin/bash
    POUBELLE=$HOME/poubelle
    
    if test ! -d $POUBELLE
    then 
       mkdir $POUBELLE
    fi
    
    case $1 in 
        -l) echo "$POUBELLE :"
    	ls $POUBELLE;;
        -s) shift
    	echo "sortir fichiers $@ de la poubelle"
    	mv "$POUBELLE/$@" ".";;
        -t) echo "taille de la poubelle"
    	du -sk $POUBELLE;;
        -v) echo "la poubelle se vide..."
    	rm -r $POUBELLE/*;;
        *)  mv $@ $POUBELLE;;
    esac 
    


top