La programmation en Korn shell

Ce fichier est dédié à la réalisation de scripts en utilisant le langage de commande korn shell. Rappelons ici qu'un script est un ensemble de commandes rassemblées dans un fichier. Aucune extension n'est requise pour celui-ci. Afin de pouvoir exécuter un script, il est impératif de le rendre exécutable (par exemple, à l'aide de la commande chmod +x).

Au menu des réjouissances, nous aborderons les sujets suivants :

  1. Justification de l'étude du Korn shell
  2. Une précaution élémentaire permettant de s'assurer que l'on travaille en korn shell
  3. L'utilisation de variables et d'expressions
    1. Les variables
    2. L'arithmétique
    3. Les arguments de ligne de commande des scripts
  4. Les structures de programmation
    1. Structures conditionelles
    2. Structures répétitives

Finalement, deux séries d'exercices sont disponibles pour vous familiariser avec la programmation shell, ici et la !

1. Pourquoi apprendre le Korn shell ?

Il existe deux grandes familles d'interpréteurs de commandes (ou shell) :

Le korn shell est de la première famille, ce qui veut dire, en particulier, que toute commande valable sous sh le sera également sous ksh.

Alors, pourquoi apprendre ksh ? La réponse est simple : les comités de normalisation d'Unix ont adopté ksh comme shell par défaut des environnements utilisateur d'Unix. Les raisons de ce choix sont simples :

2. Pour être sur de travailler en korn shell

Lorsque vous lancez un script shell, celui est interprété par votre shell courant à moins qu'il ne contienne une information spéciale spécifiant quel shell doit être utilisé. Il est très imporant de toujours fournir cette indication car elle garantit l'exécution correcte du script quel que soit l'environnement de l'utilisateur qui le lance. En effet, il serait désastreux de tenter d'exécuter des ordres ksh avec c-shell et réciproquement !

Aussi, lorsque vous écrivez un script korn shell n'oubliez jamais d'insérer l'expression suivante en première ligne, première colonne !

#!/bin/ksh

3. Utiliser des variables et des expressions

A l'instar de tout langage de programmation digne de ce nom, le Korn Shell permet d'utiliser des variables. Un identificateur de variable est une chaine de caractères commençant par une lettre et pouvant contenir des lettres, des chiffres et le caractère _. Par défaut, toute variable est de type chaîne de caractères, toutefois, nous verrons qu'il sera possible d'utiliser des expressions arithmétiques ! Attention, les noms de variables sont sensibles à la casse : var, vAR, Var et VAR sont des noms associés à des variables différentes !

3.1. Affecter une valeur à une variable

La syntaxe la plus générale pour affecter une valeur à une variable est la suivante :

let identificateur=expression

On ne peut pas faire plus simple ! Attention toutefois à ne jamais laisser d'espaces autour du signe =. Quelques explications sont néamoins nécessaires quand à la signification de "expression". En effet, cela peut etre :

3.2 Obtenir la valeur d'une variable

La valeur de la variable var est donnée par l'expression ${var}

Certains grincheux vous diront sans doute que les accolades entourant l'identificateur sont facultatives, mais elles permettent de se sortir de nombreuses situations difficiles !

Si l'identificateur var n'existe pas, alors l'évaluation ${var} renvoie une chaîne de caractères vide.

3.3 Exemples d'utilisation de variables

Sans le savoir, vous utilisez déjà régulièrement certains variables au nom prédéfini tel que PATH qui contient le chemin des exécutables directement accessibles.

Pour afficher le contenu d'une variable, on utilise la commande echo, par exemple, echo ${PATH} affiche le contenu du chemin de recherche des exécutables.

La concaténation de variables est l'opération la plus simple. Considérons, par exemple, la séquence suivante :

var1="toto"
var2="au boulot"
var3=${var1} ${var2}
echo ${var3}

Le résultat est : «toto au boulot»

Vous pouvez utiliser les variables avec toutes sortes de commandes. Par exemple :

MON_PROJET=~/c++/projet
cd ${MON_PROJET}

Typiquement, lorsque vous fixez la valeur d'une variable dans un script ou dans une session, cette modification est limitée à la session locale ou à l'intérieur du script. En revanche, il est possible d'exporter cette valeur vers l'ensemble de vos sessions en utilisant le commande export. Le cas le plus connu est celui de PATH que l'on met régulièrement à jour dans des scripts.

Juste pour conclure mon discours, je vais vous démontrer l'intérêt d'utiliser systématiquement les accolades. Supposez que vous ayez deux variables var1 et var2 entre lesquelles vous voulez insérer le chaîne "et". Vous utilisez donc la commande ${var1}et${var2}. Sans les accolades, cela devient $var1et$var2 ce qui va se solder par la valeur de var2 ! en vertu du principe précédemment expliqué sur les noms de variables manquant.

3.4 Utiliser des expressions arithmétiques

L'une des grandes faiblesses du Bourne Shell est son inaptitude à traiter des expressions arithmétiques. En effet, il lui faut les confier à une commande externe particulièrement lourde nommée expr. Cette dernière est néanmoins populaire car elle disponible sous tous les environnements : son usage est donc assez portable. Tous les autres shells proposent néanmoins des mécanismes d'évaluation d'expressions arithmétiques, le Korn Shell ne déroge pas à la règle !

3.4.1 Une commande passepartout : expr

La commande expr traite une expression arithmétique fournie en paramètre et renvoie le résultat sur la sortie standard. Elle est donc le plus souvent utilisée en conjonction avec les backquotes.

Comme nous le suggérions précédemment, la commande expr est compatible avec tous les shells. Toutefois, elle est très lourde car chaque évaluation nécessite la création d'un nouveau processus, aussi, il est impératif de la remplacer par les commandes natives du shell que vous employez si la performance est de rigueur !

Un petit exemple :

var1=10
var2=5
var3=20
var4=`expr ${var1} + ${var2}`
echo ${var4}
15
var5=`expr ${var4} / ${var2} + \( ${var3} / ${var1} \)` echo ${var5} 5

Vous noterez l'utilisation des \ avant les parenthèses afin d'éviter que celles-ci ne soient évaluées par le shell.

3.4.2 L'arithmétique en Korn Shell

L'une des caractéristiques qui a assuré le succès du Korn Shell est son arithmétique numérique qui conjugue puissance expressionnelle et facilité d'utilisation. En Korn shell, une expression arithmétique est entourée par 2 parenthèses, soit, par exemple : ((z=z+2)) qui remplace : z=`expr ${z} + 2`

La traduction en Korn Shell de l'exemple précédent est donc :

var1=10
var2=5
var3=20
(( var4 = var1 + var2 ))
echo ${var4}
15
(( var5 = var4 / var2 + ( var3 / var1 ) ))` echo ${var5} 5

Vous noterez au passage qu'il n'est plus nécessaire d'utiliser les \ devant les parenthèses !

3.5 Des variables particulières : les arguments de scripts

A l'instar de toute variable shell, les arguments de scripts sont accessibles à l'aide du caractère d'évaluation de variable : $. La table suivante exprime les différents paramètres que l'on peut utiliser.

$# Nombre de paramètres
$* Ensemble des paramètres
$0 Nom d'appel du shell
$1 -> $9 9 premiers paramètres positionnels
$$ PID du processus shell en cours

Le fait de ne pouvoir accéder qu'aux 9 premiers paramètres peut paraître surprenant et terriblement limitatif. La commande shift est là pour pallier ce problème. En effet, comme son nom l'indique, shift effectue un décalage des paramètres vers la gauche, faisant "tomber" le paramètre 1 pour le remplacer par le 2 etc ... Notons au passage que l'utilisation de shift altère la valeur de $#. L'exemple suivant (en avance sur le cours) montre comment utiliser shift, $1 et $# pour afficher l'ensemble des arguments d'un script ksh.

#!/bin/ksh

while (( $# >= 0 ))
  echo $1
	 shift
end

Quelques remarques concernant les arguments de scripts :

4. Les structures de programmation shell

4.1 Introduction

Le Korn shell propose un certain nombre de structures de programmation :

Nous n'étudierons ici que les 2 premières. L'étude de la notion de procédure et de fonction est laissée en exercice personnel.

Il est également imporant d'insister sur la rigueur de la syntaxe. La présence d'espaces ou de sauts de ligne est importante ! Aussi, et pour vous éviter des maux de tête tenances, vous recommanderai-je de vous conformer strictement à la disposition des instructions indiquée dans ce texte.

4.2 Structures conditionnelles

Comme leur nom l'indique, les structures conditionnelles permettent d'effectuer des choix ! A l'instar de leurs homologues des langages de programmation classiques, elles sont basées sur la notion d'expression booléenne. Toutefois, ici c'est la valeur 0 qui est considérée comme vraie. En effet, un programme Unix renvoie typiquement 0 lorsque son exécution s'est déroulée sans problème. Ce comportement est donc cohérent. Que va t'on pouvoir utiliser en tant qu'expression booléenne ?

Au risque de me répéter, je renchérirai sur le fait que « vrai » est associé à la valeur 0.

4.2.1 La commande test

La commande test revet un caractère particulier car, comme son nom l'indique, elle permet d'effectuer de nombreux tests aussi bien sur les fichiers, que des comparaisons numériques ou lexicographiques. Le résultat, sous forme booléenne, est renvoyé dans le statut. Il ne faut donc pas l'utiliser avec les backquotes ! Les tests sont multiples et sont de la forme : -commande argument. La table suivante indique quelles sont les commandes les plus utilisées :

Commandes permettant de tester la présence d'un fichier
-a chaine chaine est le nom d'un fichier présent dans le système
Le fichier associé peut être de n'importe quel type
Les commandes suivantes permettent de tester le type du fichier
-f chaine chaine est le nom d'un fichier normal existant
-s chaine chaine est le nom d'un fichier de taille non nulle
-d chaine chaine est le nom d'un répertoire
-b chaine chaine est le nom d'un driver bloc
-c chaine chaine est le nom d'un driver caractère
-p chaine chaine est le nom d'un tube nommé
-L chaine chaine est le nom d'un lien symbolique
Tests sur les permissions associées à un fichier
-r chaine Le fichier chaine est accessible en lecture
-w chaine Le fichier chaine est accessible en écriture
-x chaine Le fichier chaine est accessible en exécution
-k chaine Le fichier chaine dispose du bit sticky
-u chaine Le fichier chaine est en mode set uid
-g chaine Le fichier chaine est en mode set gid
Tests portant sur une chaîne de caractères
-n chaine Vrai si chaine est de longueur nulle
-z chaine Vrai si chaine n'est pas de longueur nulle
Comparaisons lexicographiques entre chaînes
chaine1=chaine2 Chaînes identiques
chaine1 !=chaine2 Chaînes différentes
chaine1 > chaine2 Comparaison supérieure au sens lexicographique
chaine1 < chaine2 Comparaison inférieure au sens lexicographique
Il est également possible d'effectuer des comparaisons numériques pour des chaînes représentant des nombres avec des opérateurs rappelant dangereusement le FORTRAN.
nombre1 -eq nombre2 test d'égalité (nombre1==nombre2)
nombre1 -ne nombre2 test de différence (nombre1 !=nombre2)
nombre1 -gt nombre2 (nombre1 > nombre2)
nombre1 -lt nombre2 (nombre1 < nombre2)
nombre1 -ge nombre2 (nombre1 >=nombre2)
nombre1 -le nombre2 (nombre1 <=nombre2)

A l'instar de expr, la commande test est disponible sous l'ensemble des shells. Toutefois, nous verrons que le Korn Shell permet d'éviter l'appel à la commande test tout en respectant sa syntaxe.

Il est également possible de combiner des expressions en utilisant :

&& Et logique
|| ou logique

La commande suivante teste si le fichier toto.txt est présent dans le répertoire courant : test -f toto.txt. Cette syntaxe étant assez lourde, il est possible de la remplacer par : [ -f toto.txt ] en prenant bien soin de séparer tous les éléments présents par des espaces.

Le Korn Shell a intègre une version interne de la commande test. Celle-ci reprend les commandes de la version externe mais est appelée avec une paire de doubles crochets, par exemple [[ -r toto.txt ]]. Bien entendu, le gain de performances est important !

4.2.2 Conditionnelle simple

La syntaxe générale est la suivante :

if condition
  then
    groupe de commandes (1)
  else
    groupe de commandes (2)
fi

où, l'on s'en doute, le groupe de commandes (1) sera exécuté si et seulement si la condition est vraie. La clause else est facultative, le groupe de commandes (2) sera éxécuté si la condition est fausse.

Il est possible de gérer plusieurs conditions gràce à la syntaxe :

if condition1
  then
    groupe de commandes (1)
  elif condition2
  then
    groupe de commandes (2)
  elif condition3
  then
    groupe de commandes (3)
  ...
  else 
    groupe de commandes (n)
fi

4.2.3 Conditionnelle multiple

Vous vous en doutez, il s'agit d'une structure à choix multiple basée sur la valeur d'un sélecteur, à la différence qu'ici, le sélecteur est une chaîne de caractères ! La syntaxe générale est la suivante :

case selecteur_chaine in
  motif 1 ) 
    groupe de commandes (1) ;;
  motif 2 )
    groupe de commandes (2) ;;
  ...
  motif n )
    groupe de commandes (n) ;;
  *)
    groupe de commandes terminal ;;
esac

Quelques remarques :

Cette structure est tout particulièrement utile pour réaliser des menus, ou traiter les options d'un script.

4.3 Structures répétitives

Vous l'aurez compris, je veux parler des boucles ! Nous allons ici étudier 3 types de boucles

  1. La boucle while classique
  2. Une boucle until à la syntaxe un peu particulière
  3. La boucle for

4.3.1 La boucle while

La syntaxe générale de cette structure itérative archi classique est la suivante :

while condition
do
  commandes
done

Où l'ensemble de commandes est exécuté tant que condition est vraie (c'est à dire en fait, égale à 0).

4.3.2 La boucle until

Sa syntaxe est la suivante :

until condition 
do
  commandes
done

Le groupe de commandes commandes est exécuté jusqu'à ce que condition soit réalisée. Notons toutefois que, contrairement à ses homologues des langages de programmation structurés, commandes n'est pas obligatoirement exécuté au moins une fois car le test se situe au début de la boucle. En fait, il s'agit uniquement d'une autre manière d'écrire la boucle while, ce qui me semble totalement dépourvu d'intérêt.

4.3.3 La boucle for

La boucle for disponible sous Korn Shell est très particulière. En effet, elle répond à la syntaxe suivante :

for variable in ensemble_de_valeurs
do
  commandes
done

La variable courante variable prend alors chacune des valeurs comprises dans ensemble_de_valeurs. On peut l'utiliser, par exemple, avec l'argument $* de la manière suivante pour afficher l'ensemble des paramètres d'un script. Cet exemple est à comparer avec celui utilisant la boucle while et présenté ci-dessus.

for param in $*
do
echo ${param}
done