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 :
Finalement, deux séries d'exercices sont disponibles pour vous familiariser avec la programmation shell, ici et la !
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 :
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
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 !
La syntaxe la plus générale pour affecter une valeur à une variable est la suivante :
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 :
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.
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.
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 !
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.
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 !
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 :
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.
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.
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 !
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
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.
Vous l'aurez compris, je veux parler des boucles ! Nous allons ici étudier 3 types de boucles
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).
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.
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