Travaux Pratiques Atelier Système D'Exploitation Fascicule N°3
Amine DHRAIEF ESEN, Université de la Manouba A.U 2016-2017
1/14
1 Évaluation d’expressions
Une part importante de la programmation de scripts repose sur une bonne compréhension des mécanismes qui permettent au shell d’évaluer correctement les expressions reçues. Nous allons donc commencer par étudier l’utilisation des variables, avant d’observer en détail les évaluations d’expressions.
1.1 Variables
La programmation sous shell nécessite naturellement des variables pour stocker des informations temporaires, accéder à des paramètres, etc. Par défaut, les variables utilisées dans les scripts shell ne sont pas typées. Le contenu d’une variable est considéré comme une chaîne de caractères, sauf si on indique explicitement qu’elle doit être traitée comme une variable entière qui peut être utilisée dans des calculs arithmétiques. De plus, le shell ne permet pas de manipuler directement de données en virgule flottante. À la différence des langages compilés habituels, une variable n’a pas à être déclarée explicitement. Dès qu’on lui affecte une valeur, elle commence à exister. Cette affectation prend la forme variable=valeur sans espaces autour du signe égal. Le message d’erreur « mavariable: command not found » est peut-être le plus célèbre parmi les utilisateurs du shell
À cause des espaces autour du signe égal, Bash a cru que l’on essayait d’invoquer la commande mavariable en lui transmettant les arguments = et 100 . La bonne syntaxe est la suivante :
Pour accéder au contenu d’une variable, il suffit de préfixer son nom avec le caractère $ . Il ne faut pas confondre ce préfixe des variables avec le symbole d’invite du shell, qui est généralement le
2/14
même caractère $ . La commande echo affiche simplement le contenu de sa ligne de commande. Le nom attribué à une variable peut contenir des lettres, des chiffres, ou le caractère souligné « _ ». Il ne doit toutefois pas commencer par un chiffre.
Ici, le shell n’a pas pu interpréter correctement cette ligne, car il a cru qu’elle se composait d’une affectation mavariable=azerty, suivie d’une commande nommée yuiop (syntaxe rarement utilisée mais autorisée). Il faut lui indiquer que les mots à droite du signe égal forment une seule chaîne de caractères. On emploie pour cela les guillemets droits :
Nous pouvons vérifier qu’une variable qui n’a jamais été affectée est considérée comme une chaîne vide
Une variable à laquelle on affecte une chaîne vide existe quand même. La différence entre une variable inexistante et une variable vide peut être mise en évidence à l’aide de certaines options des constructions de test, ou par l’intermédiaire d’une configuration particulière du shell qui déclenchera une erreur si on essaie de lire le contenu d’une variable inexistante. Cette configuration s’obtient au moyen de la commande interne set et de son option -u .
1.1.1 Délimitation du nom de variable
Tout d’abord, la construction ${variable} est une généralisation de $variable, qui permet de délimiter précisément le nom de la variable, dans le cas où on souhaiterait le coller à un autre élément. Par exemple, supposons que nous souhaitions classer les fichiers source d’une bibliothèque
3/14
Publicité
de fonctions, en leur ajoutant un préfixe qui corresponde au projet auquel ils appartiennent. Nous pourrions obtenir une séquence du type On espère obtenir « projet1source1.c » dans la variable NOUVEAUFICHIER, mais malheureusement ce n’est pas le cas :
Le fait d’avoir accolé directement $PREFIXE, le caractère souligné et $FICHIER ne fonctionne pas comme nous l’attendions : le shell a bien remarqué qu’il y a deux opérateurs $ agissant chacun sur un nom de variable, mais seul le second a été correctement remplacé. En effet, le caractère souligné que nous avons ajouté comme séparateur entre le préfixe et le nom du fichier a été considéré comme appartenant au nom de la première variable. Ainsi le shell a-t-il recherché une variable nommée PREFIXE_ et n’en a évidemment pas trouvé. La raison en est que le caractère souligné n’est pas un caractère séparateur pour le shell, et qu’on peut le rencontrer dans les noms de variables. Si nous avions utilisé un caractère interdit dans ces noms, nous n’aurions pas eu le même problème, car le premier nom de variable aurait été clairement délimité
On pourrait même utiliser les guillemets droits pour encadrer le caractère souligné afin de le séparer du nom de la variable.
Il arrive que l’on doive ajouter des caractères à la fin d’un nom de variable, et l’opérateur ${ } est alors utilisé à la place du simple $ pour marquer les limites. Ainsi, on peut utiliser :
4/14
ou des constructions comme :
1.2 Extraction de sous-chaînes et recherche de motifs
Les shells récents offrent une possibilité d’extraction automatique de sous-chaînes de caractères au sein d’une variable. La version la plus simple s’appuie sur l’option « : » de l’opérateur ${} . Ainsi l’expression ${variable:debut:longueur} est-elle automatiquement remplacée par la sous-chaîne qui commence à l’emplacement indiqué en seconde position, et qui contient le nombre de caractères indiqué en dernière position. La numérotation des caractères commence à zéro. Si la longueur n’est pas mentionnée, on extrait la sous-chaîne qui s’étend jusqu’à la fin de la variable. En voici quelques exemples :
Ce mécanisme fonctionne sans surprise, mais n’est pas aussi utile, dans le cadre de la programmation shell, que l’on pourrait le croire au premier abord. Dans nos scripts, nous manipulons en effet souvent des noms de fichiers ou des adresses réseau, et une autre possibilité d’extraction de sous-chaîne se révèle en général plus adaptée : elle repose sur l’emploi de motifs qui sont construits de la même manière que lors des recherches de noms de fichiers en ligne de commande. Ces motifs peuvent contenir des caractères génériques particuliers :
5/14
Le caractère * correspond à n’importe quelle chaîne de caractères (éventuellement vide).
Le caractère ? correspond à n’importe quel caractère.
Le caractère \ permet de désactiver l’interprétation particulière du caractère suivant. Ainsi, la séquence * correspond à l’astérisque, \? au point d’interrogation et \ au caractère backslash (barre oblique inverse).
Les crochets [ et ] encadrant une liste de caractères représentent n’importe quel caractère contenu dans cette liste. La liste peut contenir un intervalle indiqué par un tiret comme A-Z .
Si l’on veut inclure les caractères - ou ] dans la liste, il faut les placer en première ou en dernière position.
Les caractères ^ ou ! en tête de liste indiquent que la correspondance se fait avec n’importe quel caractère qui n’appartient pas à l’ensemble.
Publicité
L’extraction de motifs peut se faire en début, en fin, ou au sein d’une variable. Il s’agit notamment d’une technique très précieuse pour manipuler les préfixes ou les suffixes des noms de fichiers. L’expression ${variable#motif} est remplacée par la valeur de la variable, de laquelle on ôte la chaîne initiale la plus courte qui corresponde au motif
echo ${variable#AZE} →La portion initiale AZE a été supprimée.
echo ${variable#*T} →Tout a été supprimé jusqu’au premier T.
echo ${variable#* } → Élimination du préfixe jusqu’à la première lettre contenue dans l’intervalle.
echo ${variable#*} → Suppression de la plus petite chaîne quelconque, en l’occurrence la chaîne vide, et il n’y a donc pas d’élimination de préfixe.
L’expression ${variable##motif} sert à éliminer le plus long préfixe correspondant au motif transmis. Ainsi, les exemples précédents nous donnent :
6/14
echo ${variable##AZE} →La chaîne AZE ne peut correspondre qu’aux trois premières lettres ; pas de changement par rapport à l’opérateur précédent.
echo ${variable##*T} →le motif absorbe le plus long préfixe qui se termine par un T .
echo ${variable##* } → Le plus long préfixe se terminant par M, N, O ou P correspond à la chaîne elle-même, puisqu’elle se finit par P . L’expression renvoie donc une chaîne vide.
echo ${variable##*} → La suppression de la plus longue chaîne initiale, composée de caractères quelconques, renvoie une chaîne vide.
Les expressions ${variable%motif} et ${variable%%motif} correspondent au contenu de la variable indiquée, qui est débarrassé, respectivement, du plus court et du plus long suffixe correspondant au motif transmis. En voici quelques exemples :
1.3 Applications
7/14
Publicité
……………………………………………………………………………………………………….. ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ………………………………………………………………………………………………………
- Récupérer le nom d’hôte dans une adresse complète de machine : machine.entreprise.com
……………………………………………………………………………………………………….. ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ………………………………………………………………………………………………………
- Obtenir le nom d’un fichier débarrassé de son chemin d’accès : /usr/src/linux/kernel/sys.c
……………………………………………………………………………………………………….. ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ………………………………………………………………………………………………………
- Éliminer l’extension éventuelle d’un nom de fichier module.c
……………………………………………………………………………………………………….. ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ……………………………………………………………………………………………………… ………………………………………………………………………………………………………
1.4 Longueur de chaîne
L’opérateur $ offre aussi la possibilité de calculer automatiquement la longueur de la chaîne de caractères représentant le contenu d’une variable, grâce à sa forme ${#variable} .
8/14
Si la variable est numérique, sa longueur correspond à celle de la chaîne de caractères qui la représente. Cela est également vrai pour les variables qui sont explicitement déclarées arithmétiques. Par exemple, la variable EUID est une variable arithmétique définie automatiquement par le shell.
Les variables vides ou non définies ont des longueurs nulles.
1.5 Actions par défaut
Lorsqu’un script fonde son comportement sur des variables qui sont fournies par l’utilisateur, il est important de pouvoir configurer une action par défaut, dans le cas où la variable n’aurait pas été remplie. L’opérateur ${ } vient encore à notre aide avec l’appui de quatre modificateurs. L’expression ${variable:-valeur} prend la valeur indiquée à droite du modificateur :- si la variable n’existe pas, ou si elle est vide. Cela nous permet de fournir une valeur par défaut. Naturellement, si la variable existe et est non vide, l’expression renvoie son contenu.
9/14
L’emploi systématique de valeurs par défaut en début de script permet d’améliorer la robustesse du programme, et de le rendre plus souple, plus facile à configurer par l’utilisateur. Le contenu même de la variable n’est pas modifié. Toutefois, il est des cas où cela serait préférable. La construction $ {variable:=valeur} peut être utilisée à cet effet. Si la variable n’existe pas ou si elle est vide, elle est alors remplie avec la valeur. Ensuite, dans tous les cas, le contenu de la variable est renvoyé.
1.6 Calcul arithmétique
Publicité
Le shell permet de réaliser des calculs arithmétiques simples (on se limitera aux valeurs entières pour plus de portabilité), ce qui est parfois très précieux. Les calculs arithmétiques sont représentés par l’opérateur $((opérations)) . Les opérateurs arithmétiques disponibles sont les mêmes que dans la plupart des langages de programmation : +, -, *, et / pour les quatre opérations de base ; % pour le modulo ; << et >> pour les décalages binaires à gauche et à droite ; &, |, et ^ pour les opérateurs binaires ; Et, Ou et Ou Exclusif ; et finalement ~ pour la négation binaire. Les opérateurs peuvent être regroupés entre parenthèses pour des questions de priorité. On peut placer des blancs (espaces) à volonté pour améliorer la lisibilité du code.
Une constante numérique est considérée par défaut en base 10. Si elle commence par 0, elle est considérée comme étant octale (base 8), et par 0x comme hexadécimale (base 16). On peut aussi employer une représentation particulière base#nombre où l’on précise explicitement la base employée (jusqu’à 36 au maximum). Cela peut servir surtout pour manipuler des données binaires.
10/14
Il arrive que les données numériques d’un calcul provienne d’un programme externe ou d’une saisie de l’utilisateur, et rien ne garantit qu’elles ne commenceront pas par un zéro, même si elles sont en décimal. Supposons que l’utilisateur remplisse la variable x=010, le calcul $(($x+1)) renverrait le résultat (décimal) 9, car le shell interpréterait le contenu de x comme une valeur octale. Pour éviter ceci, on peut forcer l’interprétation en décimal avec $((10#$x+1)) . Ceci est utile lorsqu’on récupère des nombres provenant de fichiers formatés avec des colonnes de chiffres complétés à gauche par des zéros.
Les variables qui se trouvent à l’intérieur de la structure de calcul $(( )) ne sont pas tenues d’être précédées par le caractère $, mais cela améliore généralement la lisibilité du programme. Si la variable contient une chaîne de caractères représentant une opération, une différence entre les notations avec et sans $ peut apparaître par le jeu des priorités d’évaluation.
Jusque-là aucune différence. Enchaînons avec une multiplication :
11/14
La variable précédée du $ est remplacée par sa valeur littérale avant de faire l’évaluation globale. La variable sans $ est évaluée, puis son résultat est placé dans l’expression avant le calcul final. Si une variable n’est pas définie, est vide, ou contient une chaîne non numérique, elle est interprétée comme une valeur nulle. Il est aussi possible de réaliser, au sein de la structure $(( )), une ou plusieurs affectations de variables à l’aide de l’opérateur = .
Les affectations peuvent aussi se faire avec les raccourcis +=, -=, =, /=, <<=, >>=, &=, |=, ^= . Par exemple, $((a+=4)) est équivalent à $((a=a+4)) ; $((a=2)) équivaut à $ ((a=a*2)) ; ou encore $((masque&=0x01)) à $((masque=masque&0x01)) .
Une affectation renvoie la valeur calculée, ce qui permet de la réutiliser dans une autre opération : $ ((a=b+=2)) correspond à $((b=b+2)) suivie de $((a=ab)) . Bien entendu, ce genre d’écriture, franchement illisible, est totalement déconseillé. La structure $(( )) peut également servir à vérifier des conditions arithmétiques. Les opérateurs de comparaison renvoient la valeur 1 pour indiquer qu’une condition est vérifiée, et 0 sinon. Il s’agit des comparaisons classiques <, <=, >=,
-, ainsi que == pour l’égalité et != pour la différence. Les conditions peuvent être associées par un Et Logique && ou un Ou Logique ||, ou encore être niées avec ! .
12/14
1.7 Invocation de commande
Une dernière construction à base de l’opérateur $ est appelée « substitution de commande ». Elle a
la forme suivante : $(commande) . La forme commande, probablement héritée du shell C,
est quant à elle peu recommandée, car moins lisible et difficile à imbriquer. La commande qui se
trouve entre les parenthèses est exécutée, et tout ce qu’elle a écrit sur sa sortie standard est capturé
et placé à son emplacement sur la ligne de commande. Par exemple l’invocation :
Pour afficher correctement le contenu d’une variable qui renferme des espaces multiples, des tabulations et des retours à la ligne, il faut que ce contenu soit protégé de l’interprétation du shell, ce qui s’obtient en l’encadrant par des guillemets.
13/14
14/14