A la découverte d'UNIX (FreeBSD)

A la découverte d'UNIX (FreeBSD)

Mis à jour le mardi 8 janvier 2013
  • 4 semaines
  • Moyen

Le ksh, ou Korn shell, du nom de son développeur, fut la réponse d'AT&T aux csh et tcsh, dont il a repris certaines fonctionnalités.

Pour des scripts plus compliqués, avec des fonctions, des flux de redirection (avec des |), des signaux système ou encore des sous-processus, le ksh est parfois plus pratique que ses principaux "concurrents".

Beaucoup d'UNIX l'intègrent par défaut. Sous FreeBSD, vous pouvez l'installer avec :

[Nom de l'ordinateur]# pkg_add -r ksh93

ou

[Nom de l'ordinateur]# cd /usr/ports/shells/ksh93 && make install clean

Le 93 signifie que vous n'installez pas le ksh original, de 1983, mais sa version améliorée de 1993.

Si vous utilisez un UNIX pour qui ksh est présent par défaut, vous commencerez vos scripts par la ligne :

#!/bin/ksh

Par contre, sur un OS comme FreeBSD, pour qui ksh est un shell importé, cette ligne devient :

#!/usr/local/bin/ksh93

A - Saisie, affichage et conditions

Les principes généraux vus avec le csh restent valables en ksh. Il y a toujours des variables, des conditions, des boucles, des calculs, des tableaux, des arguments, etc. Mais les syntaxes changent et s'inspirent moins du C que du précédent shell "maison" d'AT&T : le sh.

Par exemple, pour lire ce que l'utilisateur entre au clavier, on utilise l'instruction read. Notre script saisie (voir le chapitre Vos premiers scripts) deviendra donc, en ksh :

#!/usr/local/bin/ksh93

echo "Saisissez un nombre ou un mot : "
read reponse
echo 'Vous avez saisi '$reponse

Le bloc if, lui, est assez différent en ksh :

if [[ condition ]]; then
   instructions à exécuter si la condition est vraie
else
   instructions à exécuter si la condition est fausse
fi

Et l'égalité ne se teste pas avec ==. Pour comparer des nombres, on utilise -eq. Et pour tester si deux chaines de caractères sont identiques, c'est un = simple.

Essayez le script devinette.ksh :

#!/usr/local/bin/ksh93
                            
echo "Combien font six fois sept ?"
read reponse

if [[ $reponse -eq 42 ]]; then
   echo "C'est la bonne reponse, bravo !"
else
   echo "Non, ce n'est pas la bonne reponse."
fi

Autres conditions que if peut tester :

  • [[ a -ne b ]] : a différent de b

  • [[ a -lt b ]] : a < b

  • [[ a -gt b ]] : a > b

Les symboles && (ET) et || (OU) sont les mêmes qu'en csh. Par contre, si vous voulez inclure l'équivalent d'un else if, il faut écrire elif :

#!/usr/local/bin/ksh93

echo "Combien font six fois sept ?"
read reponse

if [[ $reponse -eq 42 ]]; then
   echo "C'est la bonne reponse, bravo !"
elif [[ $reponse -gt 42 ]]; then
   echo "Vous etes au dessus de la bonne reponse."
else
   echo "Vous etes en dessous de la bonne reponse."
fi

Toujours dans les conditions, switch devient case :

case $variable in
    valeur 1) instruction à exécuter si variable a la valeur 1;;
    valeur 2) instruction à exécuter si variable a la valeur 2;;
    valeur 3|valeur 4) instruction à exécuter si variable a la valeur 3 ou la valeur 4;;
    *) instruction à exécuter si variable n'a aucune des valeurs ci-dessus;;
esac

esac ? késaco ? :o

C'est juste case à l'envers. De la même manière, on referme un if par un fi.

#!/usr/local/bin/ksh93
                            
echo "Tapez le nom d'un mois de l'annee en minuscules : "
read mois

case $mois in
   janvier|juin|juillet) print "Ce nom commence par un 'j'.";;
   fevrier) print "Brrr ! Il fait froid !";;
   mars|avril) print "Ne te decouvre pas d'un fil";;
   aout) print "Quelle chaleur !";;
   septembre) print "C'est la rentree.";;
   octobre|novembre) print "Ah... l'automne !";;
   decembre) print "Joyeux Noel !";;
   *) print "On vous a dit le nom d'un mois en minuscules.";;
esac

Vous remarquez la commande print. C'est encore une autre manière d'afficher du texte.

B - Variables, arguments et tableaux

Bien sûr, on n'a pas toujours besoin de read pour affecter une valeur à une variable. On peut aussi lui donner cette valeur directement, avec un simple =. Et si la valeur voulue est une chaîne de caractères, on met des guillemets :

a="champignon"

Pour les calculs, on utilise des double-parenthèses.

#!/usr/local/bin/ksh93
                            
a=3
((b=a*2))
echo $b

Ce qui affiche : 6

En ksh, les arguments fournis au script sont traités comme des variables isolées et pas comme un tableau :

$1 est le premier argument. $2 est le deuxième, etc. $# est le nombre total d'arguments. Et, si on veut tous les afficher d'un coup, c'est echo "$*".

Et voici d'autres variables spéciales, qui vous serviront peut-être un jour :

  • $$ : Le PID du script. Si vous voulez faire appel à kill, par exemple.

  • RANDOM : Un nombre au hasard.

  • Les variables d'environnement habituelles.

Les tableaux

On utilise toujours les parenthèses pour les déclarer. Là encore, attention à ne pas laisser d'espaces autour du signe =.

#!/usr/local/bin/ksh93

nom=(Twm Fluxbox KDE LXDE Xfce GNOME Enlightenment)
print ${nom[*]}
print "premiere case : ${nom[0]}"
print "deuxieme case : ${nom[1]}"
echo "Ce tableau comporte ${#nom[*]} cases."

Quand vous demandez l'affichage du tableau, n'oubliez pas les accolades { }. Elles sont également nécessaires pour une variable simple si vous affichez du texte derrière.

Repérez enfin la variable #nom[*], qui contient le nombre de cases dans le tableau nom.

C - Les boucles

Il y a trois types de boucles en ksh :

  • while

  • until

  • for (équivalent du foreach de csh, et pas des boucles for qu'on rencontre en C).

while

Comme en csh, la boucle while tourne tant qu'une certaine condition est vraie. Sa structure générale est la suivante :

while [[ condition ]]; do
   instruction à répéter
   ...
   autres instructions à répéter
   ...
done

Parmi les instructions de la boucle, vous rencontrerez peut-être continue et/ou break. Laissez-moi vous les présenter. :)

continue et break n'apparaissent que dans une boucle et servent à l'interrompre prématurément. Mais attention, ils sont différents : continue permet de passer tout de suite au prochain tour de la boucle, sans finir le tour actuel, tandis que break vous fait carrément sortir de la boucle.

#!/usr/local/bin/ksh93

i=1
while [[ i -lt 6 ]]; do
   print Tour de boucle numero "$i"
   print Tapez '1 (ou autre chose)' pour continuer ce tour
   print Tapez '2' pour passer au tour suivant
   print Tapez '3' pour quitter la boucle

   read reponse
   case $reponse in
      2) continue;;
      3) break;;
      *) print OK, on continue;;
   esac

   echo Suite de la boucle numero "$i"
   ((i++))
done
print Nous sommes sortis de la boucle.

Ce script donnera :

% boucle.ksh
Tour de boucle numero 1
Tapez 1 (ou autre chose) pour continuer ce tour
Tapez 2 pour passer au tour suivant
Tapez 3 pour quitter la boucle
1
OK, on continue
Suite de la boucle numero 1
Tour de boucle numero 2
Tapez 1 (ou autre chose) pour continuer ce tour
Tapez 2 pour passer au tour suivant
Tapez 3 pour quitter la boucle
f
OK, on continue
Suite de la boucle numero 2
Tour de boucle numero 3
Tapez 1 (ou autre chose) pour continuer ce tour
Tapez 2 pour passer au tour suivant
Tapez 3 pour quitter la boucle
2
Tour de boucle numero 3
Tapez 1 (ou autre chose) pour continuer ce tour
Tapez 2 pour passer au tour suivant
Tapez 3 pour quitter la boucle
3
Nous sommes sortis de la boucle.

Remarquez que le quatrième tour de boucle est annoncé comme le "tour n°3". En effet, à cause du continue, l'instruction ((i++)) n'a pas été lue, cette fois-ci.

until

La boucle until est similaire à while, mais avec cette différence :

Elle tourne jusqu'à ce qu'une certaine condition soit vraie. Ainsi, le script que voici est équivalent au précédent :

#!/usr/local/bin/ksh93

i=1
until [[ i -eq 6 ]]; do
   print Tour de boucle numero "$i"
   print Tapez '1 (ou autre chose)' pour continuer ce tour
   print Tapez '2' pour passer au tour suivant
   print Tapez '3' pour quitter la boucle

   read reponse
   case $reponse in
      2) continue;;
      3) break;;
      *) print OK, on continue;;
   esac

   echo Suite de la boucle numero "$i"
   ((i++))
done
print Nous sommes sortis de la boucle.
for

Comme le foreach de csh, la boucle for de ksh affecte successivement plusieurs valeurs à une variable.

for variable in valeur1 valeur2 valeur3 valeur4; do
   instructions à répéter
   ...
done

Dans cet exemple, nous allons faire appel à tradMois, le script csh du chapitre précédent. Vous allez voir que ksh et csh peuvent tout à fait collaborer :

#!/usr/local/bin/ksh93

for mois in Jan Feb Mar Apr; do
   echo `tradMois $mois`
done

Résultat :

% exemplefor.ksh
janvier
fevrier
mars
avril

D - Les fonctions

En csh, il n'y avait pas vraiment de fonctions. On pouvait les simuler en appelant un script à l'intérieur d'un autre. C'est ce que je vous ai montré en appelant tradMois à l'intérieur de rapport. Cela nous oblige à découper le programme qu'on veut réaliser en plusieurs fichiers de scripts : un par fonction. Ce n'est pas forcément plus mal : c'est une bonne manière de s'organiser. Mais ça ne plait pas à tout le monde.

En ksh, il y a de vraies fonctions. Et on peut tout mettre dans un seul fichier, si on veut (au risque de ne plus s'y retrouver).

D'accord... Mais c'est quoi, une fonction ? o_O

C'est un petit bout de programme, une série d'instructions, que l'on peut appeler à plusieurs reprises dans le script, en lui donnant éventuellement des arguments.

Pour créer une fonction, on écrit :

mafonction() {
   instructions de la fonction
   ...
}

Et un peu plus loin dans le script, on l'appelle de cette manière :

mafonction argument1 argument2

À l'intérieur de la fonction, on peut utiliser les arguments qu'on lui a transmis. Ils sont désignés par $1, $2, etc. Ce qui signifie qu'on ne peut pas utiliser dans la fonction les arguments du script principal, à moins de les lui transmettre explicitement.

tradMois()

Pour nous exercer, nous allons traduire en ksh le script rapport. Il faut commencer par transformer tradMois en une fonction ksh :

#!/usr/local/bin/ksh93

tradMois() {
   court=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
   long=(janvier fevrier mars avril mai juin juillet aout\
         septembre octobre novembre decembre)
   i=0
   until [[ $i -eq 12 ]]; do
      if [[ $1 = ${court[$i]} ]]; then
         echo ${long[$i]}
      fi
      ((i++))
   done

}


#Appel de la fonction, avec l'argument Dec, pour décembre
tradMois Dec

Ce qui affiche : decembre.

On s'est servi d'une boucle until pour faire varier i de 0 à 12. Au début du dernier tour de boucle, i vaut 11. Et à la fin de ce même tour, i = 12, donc la condition du until devient vraie et on n'entre plus dans la boucle.

Le $1 est l'argument que vous avez transmis à la fonction, c'est-à-dire Dec.

Remarquez aussi que la fonction doit obligatoirement être implémentée (on doit écrire son contenu) avant d'être appelée. En effet, je vous rappelle que les shells sont des langages interprétés donc, si vous l'appelez avant de la décrire, l'ordinateur ne la connaîtra pas.

rapport.ksh

Passons maintenant au script principal. Je vous rappelle que son rôle est d'analyser un fichier et de vous en indiquer le propriétaire, la date de dernère modification, le nombre de lignes, etc. On doit lui donner le nom de ce fichier en argument.

La structure est celle du script csh rapport. Mais vous allez remarquer plusieurs différences syntaxiques. Par exemple, tous les numéros de cases des tableaux sont décalés puisqu'on commence à 1 en csh et à 0 en ksh.

#!/usr/local/bin/ksh93

tradMois() {
   court=(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
   long=(janvier fevrier mars avril mai juin juillet aout\
         septembre octobre novembre decembre)
   i=0
   until [[ $i -eq 12 ]]; do
      if [[ $1 = ${court[$i]} ]]; then
         mois=${long[$i]}
         return 3
      fi
      ((i++))
   done
}

tabls=(`ls -l $1`)
tabwc=(`wc $1`)

user=${tabls[2]}
groupe=${tabls[3]}
jour=${tabls[6]}
heure=${tabls[7]}
taille=${tabls[4]}

lignes=${tabwc[0]}
mots=${tabwc[1]}

tradMois ${tabls[5]}

print "Le fichier $1 appartient a l'utilisateur $user et au groupe $groupe.\n\
Sa derniere modification date du $jour $mois a $heure.\n\
Il pese $taille Ko et comporte $mots mots repartis en $lignes lignes.\n"

J'ai modifié légèrement la fonction tradMois() car, cette fois, le but n'est pas d'afficher le nom du mois mais de l'affecter à la variable mois. Et une fois que cette affectation est faite, il est inutile de continuer la boucle. J'ai donc ajouté l'instruction return, qui vous fait sortir immédiatement de la fonction et "retourne" une valeur.

Là, j'ai choisi de renvoyer la valeur 3. Ce n'est pas très important vu que je ne m'en sers pas après. Sachez cependant que la valeur retournée est ensuite brièvement disponible sous le nom de $?.

Essayons le script :

% rapport.ksh saisie.ksh
Le fichier saisie.ksh appartient a l'utilisateur brice et au groupe brice.
Sa derniere modification date du 19 juin a 18:39.
Il pese 500 Ko et comporte 75 mots repartis en 16 lignes.

Bravo ! Vous avez réussi votre première fonction ksh. :D

Je ne vous ai pas présenté ici toutes les possibilités du ksh, ni des scripts en général. Mais vous avez vus les principaux outils, avec lesquels on peut faire pleiiiiin de choses ! Maintenant, à vous de faire travailler votre imagination. ;)

Découvrez aussi ce cours en...

Exemple de certificat de réussite
Exemple de certificat de réussite