Accueil > Dossiers > UNIX

UNIX/LINUX
(tutorial 4 - scripts)

But de ce tutorial

Au-dessus des infoducs sur les filtres on peut considérer les scripts, qui permettent de structurer des éléments eux-mêmes complexes. On touche ici à quelque chose se rapprochant plus de la programmation classique.


Structures utilisées dans le présent tutorial, par ordre d'apparition

if : then / else ; case in ; while do ; for in... Avec quelques scripts classiques : calcul d'une factorielle, calculette, filtrer les fichiers.

SOMMAIRE DU TUTORIAL 4
a) Un simple script de test
b) Utiliser des filtres
c) Développer l'interactivité et le test multiple
d) Faire une boucle
e) Traiter une liste d'éléments


a) Un simple script de test >> vers le sommaire
Il existe plusieur shell pour interpréter un script : Bash, ksh, zsh... La première ligne peut donc spécifier le type de shell dont on a besoin. A l'heure actuelle, zsh est le plus complet. Donc :
#!/bin/zsh

Cette ligne spécifiera le shell qui doit exécuter le script. Maintenant, on veut faire un simple test en affichant quelques uns des arguments parmis ceux qui donnent l'utilisateur. Ainsi :
#!/bin/zsh
if [[ $# -eq 0 ]]
      
then exit 1
fi
# Ceci est un commentaire, bien précédé de #. Un test "si" a la structure suivante :
#
if [[ condition ]]
#     
then action1
#     
else action2
#
fi
if [[ $1 == "aller" ]]
       
then
             
if [ $# -gt 1 ]
                   
then echo Nous allons dans le repertoire $2
                          
cd $2
                   
else echo Specifiez simplement le repertoire...
              
fi
         
else
             
echo "Vous ne voulez aller dans aucun repertoire. Imprevu..."
             
echo "Les parametres d'appel du script $0 sont $*"
fi

Si ce script n'a aucun argument, il va se fermer (condition $# -eq 0). Sinon, on continue. Si le premier argument est "aller", alors on regarde :
- si on a d'autres arguments, le deuxième est un nom de répertoire où on se déplace
- s'il n'a pas plus d'un argument, on affiche un message demandant de spécifier le répertoire
Enfin, si le premier argument n'est pas "aller" on affichera le nom du script et ses paramètres d'appels.

On remarquera donc que $# donne le nombre de paramètres, $* la liste complète, et $n permet de sélectionner le n-ième paramètre. On a d'autres possibilités comme $$ qui donne l'identifiant du processus, ou $? qui permet de connaître le statut de sortie de la dernière commande (nous avons fait un exit 1 : le statut après cela est de 1).
Dans la condition, on remarquera :
- -eq pour l'égalité
- -lt pour inférieur (less than)
- -gt pour supérieur (greater than)
- -ne pour différent (not equal)
- = et != pour tester une égalité avec une chaîne de caractères

 

b) Utiliser des filtres >> vers le sommaire
On veut faire un script qui affiche le contenu d'un fichier situé entre deux colonnes. Par exemple, l'utilisation de ce script sera couper.sh texte.txt 3 10 ; en résultat on affichera le contenu du fichier se trouvant entre le 3ème et le 10ème caractère (colonne). D'où :
#!/bin/zsh
if [[ $# -ne 3 ]]
       
then print "Donnez un nom de fichier et deux entiers."
       
elif [[ -f $1 ]]
            
then print "Le fichier existe, nous allons le traiter."
               
if [[ $4 -gt $3 ]]
                  
then cut -c $3-$4 $1
                  
else print "$3 est plus grand que $4, incorrect"
               
fi
            
else
               
print "Acces impossible a $1"
        
fi
fi

Si nous n'avons pas au moins 3 paramètres, on ne peut pas faire le script. Après, on vérifie que ce soit un fichier et qu'on demande des colonnes logiques pour passer les arguments au filtre cut.

En effet, le if possède d'autres possibilités :
- -d $n pour vérifier si $n est un répertoire
- -f $n pour vérifier si $n est un fichier
- -x $n pour vérifier si $n est un fichier exécutable

 

c) Développer l'interactivité et le test multiple >> vers le sommaire
Le but est ici de créer un script de type calculette. On lui donne un nombre, un opérateur puis un nombre et il fait l'opération. Par exemple calcul.sh 2 + 3 doit renvoyer 5. D'où :
#!/bin/zsh
if [[ $# -ne 3 ]]
     
then print "Il faut 3 parametres. Ni plus, ni moins..."
     
else
         
if [[ $2 == "*" ]]
            
then expr $1 \* $3
         
fi
         
if [[ $2 == "/" ]]
            
then expr $1 / $3
         
fi
         
if [[ $2 == "+" ]]
            
then expr $1 + $3
         
fi
         
if[[ $2 == "-" ]]
            
then expr $1 - $3
         
fi
fi

On fait ici appel à la commande expr qui permet d'évaluer une expression (consultez son manuel pour plus d'informations). Il est stupide lorsqu'on sait par exemple que $2 est égal à "*" de regarder s'il est ensuite égal à /, + ou -. Cependant, quelles possibilités ? Il faudrait soit faire des else-if en cascade, soit sortir après le then (avec un exit 0 par exemple).

On propose d'utiliser une autre structure qui permet d'écrire de façon propre une série de tests sur une variable :
#!/bin/zsh
if [[ $# -ne 3 ]]
   
then exit 1
fi
# On s'assure d'avoir nos trois opérateurs
case $2 in
     +)
expr $1 + $3;;
     -)
expr $1 - $3;;
     /)
expr $1 / $3;;
     "*")
expr $1 \* $3;;
     *)
print "Aucun operateur conforme selectionne";;
esac

Cette structure permet de tester l'égalité de la variable parmis plusieurs possibilités ; en C++ ou Java, on fait un switch. Si aucune des possibilités n'est vérifiée alors on peut disposer d'un cas par défaut, marqué *). Pour éviter dans notre situation que celui-ci remplace l'opérateur * de la multiplication, on spécifie bien avant que notre opérateur de multiplication est le caractère *.

On souhaite maintenant développer l'interactivité en saisissant les données de l'utilisateur au fur et à mesure de l'opération, c'est-à-dire ne plus passer d'arguments. Ainsi :
opre une série de tests sur une variable :
#!/bin/zsh
# On ne s'occupe plus des arguments
print "Premier nombre ? "
read NOMBRE1
print "Second nombre ? "
read NOMBRE2
print "Operateur ? "
read OPERATEUR
case $OPERATEUR in
     +) RESULT=$(
expr $NOMBRE1 + $NOMBRE2);;
     -) RESULT=$(
expr $NOMBRE1 - $NOMBRE2);;
     /) RESULT=$(
expr $NOMBRE1 / $NOMBRE2);;
     "*") RESULT=$(
expr $NOMBRE1 \* $NOMBRE2);;
     *)
print "Operateur non conforme";;
esac
print "$NOMBRE1 $OPERATEUR $CHIFFRE2 = $RESULT"

Avec read on lit une entrée au clavier que l'utilisateur va taper, et on la stocke dans une variable qu'on déclare du même coup.

 

d) Faire une boucle >> vers le sommaire
On aimerait parfois pouvoir répéter certains ennoncés un nombre de fois donner, c'est-à-dire réaliser simplement une boucle. Pour cela, on utilise la structure suivante :
while [[ condition ]]
do
      action
done

Par exemple, pour compter de 0 à 10 :
NOMBRE=0
while [[ $NOMBRE -lt 11 ]]
       print "$NOMBRE"
       NOMBRE=$(expr $NOMBRE + 1)
done

On peut aussi faire script pour calculer une factorielle, tel que n! = n*(n-1)*(n-2)*...*1 :
#!/bin/zsh
if [[ $# -ne 1 ]] || [[ $1 -lt 0 ]]
       
then print "On ne peut calculer une factorielle qu'en ayant un nombre positif"
       
else
           RESULT=1
           N=$1
           
while [[ $N -gt 1]]
           
do
                 RESULT=$(
expr $RESULT * N)
                 N=$(
expr $N - 1)
           
done
           
print "factoriel($1)=$RESULT"
fi

Sur le même principe, on peut faire clignoter la LED (diode) du clavier un certain nombre de fois, avec un filtre cligno.sh qui reçoit en argument le nombre de fois :
#!/bin/zsh
if [[ $# -ne 1 ]]
       
then print "Definissez le nombre de fois a clignoter"
       
else
           CLIGNO=-1
           
while [[ $CLIGNO -lt $0]]
           
do
                 
xset -led 3
                 CLIGNO=$(
expr $CLIGNO + 1)
                 
xset led 3
           
done
fi

 

e) Traiter une liste d'éléments >> vers le sommaire
Une structure nous permet de faire une action sur chaque élément d'un ensemble jusqu'à avoir parcouru tout l'ensemble :
for élément in ensemble
do
      action
done

Par exemple, l'extrait de script suivant va parcourir la liste des paramètres et les afficher un par un :
for i in $*              #rappel : $* est la liste des paramètres fournis au script
do
      print "$i"
done

L'autre usage simple est la liste de documents. Par exemple, on souhaite afficher le nom de tous les fichiers exécutables du répertoire donné en paramètre :
#!/bin/zsh
if [[ $# -ne 1 ]]; then exit 1
for var in $(ls $1)
do
       
if [[ -x $var ]]
            
then print $var
       
fi
done

Il y a bien entendu toujours d'autres moyens que d'utiliser cette structure avec la liste pour faire une opération équivalente. Mais les autres possibilités sont en général moins rapides, quand elles ne relèvent pas du bidouillage... Le script précédent, en remplacant -x par -f permet d'afficher tous les fichiers. Faisons un script qui fait de même sans utiliser cette structure, avec ce que nous savons :
#!/bin/zsh
nombre=$(
ls -als | wc -l)
#nombre représente le nombre d'éléments listés par la commande ls -als
i=1
while [[ $i -lt $nombre ]]
do
      CHAR=$(
ls -als | head --lines="$i" | tail -1 | cut -c 6-6)
      
if [[ $CHAR != "d" ]]; then ls -1 | head --lines="$i" | tail -1; fi
      i=$(
expr $i + 1)
done

On affiche la liste avec les permissions de tous les éléments. Si un élément n'est pas un répertoire (pas de d), on considère qu'il est un fichier et on affiche alors sa ligne. En executant ceci, on sera frappé par l'extrême-lenteur face à la méthode rapide, et propre, présentée avec la structure précédente.