Vous êtes sur la page 1sur 275

Shells

Linux et Unix
par la pratique

Christophe

Blaess

Linux et Unix

Shells

par la pratique

CHEZ LE MME DITEUR C. Blaess. Programmation systme en C sous Linux. N11054, 2002, 932 pages. D. Taylor. 100 scripts shell Unix. N11483, 2004, 366 pages. D. Taylor. Unix shell. N11147, 2002, 350 pages. M. KraffT, adapt par R. HerTzog et R. Mas, dir. N. MaKarviTcH. Debian. Administration et configuration avances. N11904, 2006, 674 pages. R. HerTzog, C. Le Bars, R. Mas. Debian. GNU/Linux. N11639, 2005, 298 pages I. HurBain. Mmento Unix/Linux. N11954, 2006, 14 pages. J.-F. BoucHaudy. Linux Administration Tome 1. N12037, 2006, 200 pages. J.-F. BoucHaudy. Linux Administration Tome 2. N12154, 2007, 400 pages. J.-F. BoucHaudy, G. gouBeT. Linux Administration (format semi-poche). N12074, 2007, 800 pages. s. Blondeel, d. carTron, J. risi. Dbuter sous Linux avec Mandriva. N11689, 2006, 530 pages. B. Caccinolo, L. DricoT, J. MarKoll. Ubuntu. Une distribution Linux facile utiliser. N11608, 2006, 360 pages. P. cegielsKi. Conception des systmes dexploitation - Le cas Linux. N11360, 2003, 606 pages. J.-F. BoucHaudy. TCP/IP sous Linux. N11369, 2003, 866 pages. B. BouTHerin, B. delaunay. Scuriser un rseau Linux (3e dition). N11960, 2007, 250 pages. V. sTanfield, r.W. sMiTH. Linux - Guide de ladministrateur. N11263, 2003, 654 pages. B. HaTcH, J. lee, g. KurTz. Halte aux hackers Linux. N25487, 2003, 660 pages. C. Aulds. Apache 2.0 - Guide de ladministrateur Linux. N11264, 2003, 612 pages. C. HunT. Serveurs rseau Linux. N11229, 2003, 648 pages. P. ficHeux. Linux embarqu (2e dition). N11674, 2005, 330 pages.

Linux et Unix

Shells

par la pratique

Christophe

Blaess

DITIONS EYROLLES 61, bd Saint-Germain 75240 Paris Cedex 05 www.editions-eyrolles.com

Le code de la proprit intellectuelle du 1er juillet 1992 interdit en effet expressment la photocopie usage collectif sans autorisation des ayants droit. Or, cette pratique sest gnralise notamment dans les tablissements denseignement, provoquant une baisse brutale des achats de livres, au point que la possibilit mme pour les auteurs de crer des uvres nouvelles et de les faire diter correctement est aujourdhui menace. En application de la loi du 11 mars 1957, il est interdit de reproduire intgralement ou partiellement le prsent ouvrage, sur quelque support que ce soit, sans autorisation de lditeur ou du Centre Franais dExploitation du Droit de Copie, 20, rue des Grands-Augustins, 75006 Paris. Groupe Eyrolles, 2008, ISBN : 978-2-212-12273-2

Avant-propos
Sur les systmes Linux et Unix actuels, lutilisateur est gnralement confront en premier lieu un environnement graphique disposant de navigateurs Internet, doutils graphiques pour parcourir les rpertoires et visualiser le contenu des chiers, dapplications pour la bureautique, de jeux, etc. Le shell ne constitue plus ncessairement le premier contact entre lutilisateur et le systme. Pourtant il sagit toujours dun passage oblig pour qui veut matriser et administrer correctement une machine Linux ou Unix. Le shell est tout dabord une interface efcace pour passer des ordres ou des commandes au systme. Il est plus rapide demployer une ligne comme
$ cp /tmp/fic-0* /home/user/test/

que de lancer un gestionnaire de chiers, se placer dans le rpertoire source, slectionner les chiers intressants, utiliser la commande Copier, se dplacer dans le rpertoire destination et utiliser la commande Coller. Hormis laspect defcacit et de rapidit des commandes, le shell est un outil extrmement puissant puisquil permet de programmer des actions excutes intelligemment et automatiquement dans de nombreuses situations : dmarrage du systme (boot), tches administratives, lancement dapplication, analyse de chiers journaux, etc. Nous verrons dans ce livre quil est galement possible dcrire des scripts shell pour programmer de vritables petites applications utiles au quotidien et facilement personnalises par lutilisateur. Le langage de programmation du shell est assez ardu, peu intuitif et peu tolrant, aussi conseillerai-je au lecteur de mettre le plus vite possible ses connaissances en pratique, en faisant tourner les exercices et les exemples, en les modiant, en les personnalisant. Le code des exemples, des exercices corrigs et des scripts supplmentaires est disponible ladesse Web suivante : http://www.blaess.fr/christophe. Je tiens remercier tout ceux qui mont aid rdiger ce livre. Outre le soutien de mes proches pour mnager le temps ncessaire sa rdaction, je voudrais insister sur la qualit et la pertinence des remarques concernant mes prcdents ouvrages sur les scripts qui mont permis denrichir celui-ci. Je remercie pour cela Eric Berthomier, Laurent et Pierre Bourquard, Yannick Cadin, Michel Llibre, Franois Micaux, Davy NGuyen et bien dautres.

Table des matires


Avant-propos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 1

Principes des scripts Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Le shell Unix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Pourquoi crire un script shell ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Outils ncessaires . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

1 2 4 5 5 6 7 9 11 11

Excution dun script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Invocation de linterprteur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Appel direct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ligne shebang. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 2

Programmation Shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Premier aperu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Premier script, rm_secure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Analyse dtaille . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Performances . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exemple dexcution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

13 14 14 16 23 24 26 26

VIII

Shells Linux et Unix par la pratique

CHAPITRE 3

valuation dexpressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Prcisions sur loprateur $ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29 11 13 26 29 30 41 41 45 49 50 51 51 53 54 57 57

Calcul arithmtique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Invocation de commande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Portes et attributs des variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Paramtres . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Paramtres positionnels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Paramtres spciaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Protection des expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Protection par le caractre backslash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Protection par apostrophes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Protection par guillemets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . valuation explicite dune expression . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 4

lments de programmation shell . . . . . . . . . . . . . . . . . . . . . . . . . .


Commandes et code de retour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Commande simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pipelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Listes de pipelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Commandes composes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

77 77 77 79 81 89 90 90 91 100 104 104

Redirections dentres-sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Entres-sorties standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Redirection des entres et sorties standards . . . . . . . . . . . . . . . . . . . . . . . . Redirections avances. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Structures de contrle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Slection dinstructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Table des matires

IX
112 120 127 127

Itrations dinstructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 5

Commandes, variables et utilitaires systme . . . . . . . . . . . . . . .


Commandes internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Comportement du shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Excution des scripts et commandes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interactions avec le systme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arguments en ligne de commande . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Variables internes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

131 132 132 133 136 141 147 150 153 153

Commandes externes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 6

Programmation shell avance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Processus ls, paralllisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrire-plan et dmons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Envoi dun signal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Rception dun signal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Attente de signaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

155 155 160 163 164 165 166 167 170 171 172 174 174 175 176

Communication entre processus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Entres-sorties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


tee . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xargs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Interface utilisateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
stty . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . tput . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . dialog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Shells Linux et Unix par la pratique

Dboguer un script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Virgule ottante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 7

176 177 178

Expressions rgulires Grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Expressions rgulires simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Expressions rationnelles tendues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

179 179 180 189 190 192 193 193

Outil grep . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Recherche rcursive avec nd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 8

Sed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Prsentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilisation de Sed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Principe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fonctionnement de Sed. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Commandes Sed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

195 195 196 197 198 201 210 210

Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CHAPITRE 9

Awk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Fonctionnement de Awk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Les motifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

213 213 214 215 217 217 217 220

Enregistrements et champs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Les enregistrements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les champs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

Table des matires

XI
224 226 227 232 232

Structures de contrle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Retour sur les afchages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


CHAPITRE 10

Bonne criture dun script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Prsentation gnrale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gestion des erreurs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Les fonctions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Exercice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE A

233 233 235 236 239 240 241

Solutions des exercices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


Chapitre 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 7 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Chapitre 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ANNEXE B

243 243 244 245 248 250 252 254

Bibliographie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Livres et articles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sites de rfrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Norme Single UNIX Specication Version 3 . . . . . . . . . . . . . . . . . . . . . . Bash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

255 255 256 256 256

XII

Shells Linux et Unix par la pratique

Korn shell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pdksh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Tcsh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zsh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sed . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

256 256 257 257 257 259

1
Principes des scripts Shell
Avant de commencer notre tude des scripts shell pour Unix et Linux, jaimerais prciser quelques lments de vocabulaire que jemploierai couramment et qui ne sont pas toujours trs intuitifs pour le lecteur profane. Tout dabord, nous tudierons la programmation de scripts. Un script est chier contenant une srie dordres que lon va soumettre un programme externe pour quil les excute. Ce programme est appel interprteur de commandes. Il existe de nombreux interprteurs de commandes. Naturellement, le shell en fait partie, tout comme certains outils tels que Sed et Awk que nous verrons ultrieurement, ainsi que dautres langages tels que Perl, Python, Tcl/Tk, Ruby, etc. Ces langages sont dits interprts, par opposition aux langages compils (comme C, C++, Fortran, Ada, etc.). Leurs principales diffrences sont les suivantes : Aprs lcriture dun chier script, il est possible de le soumettre directement linterprteur de commandes, tandis quun code source crit en langage compil doit tre traduit en instructions de code machine comprhensibles pour le processeur. Cette tape de compilation ncessite plusieurs outils et peut parfois savrer longue. Le code compil tant directement compris par le processeur du systme, son excution est trs rapide, alors quun script doit tre interprt dynamiquement, ce qui ralentit sensiblement lexcution. Le chier excutable issu dune compilation est souvent volumineux et nest utilisable que sur un seul type de processeur et un seul systme dexploitation. linverse, un chier script est gnralement assez rduit et directement portable sur dautres processeurs ou dautres systmes dexploitation pour peu que linterprteur de commandes correspondant soit disponible.

Shells Linux et Unix par la pratique

Un chier compil est incomprhensible par un lecteur humain. Il nest pas possible den retrouver le code source. Cela peut garantir le secret commercial dun logiciel. Inversement, un chier script est directement lisible et modiable, et peut contenir sa propre documentation sous forme de commentaires, ce qui est intressant dans le cadre des logiciels libres.

Le shell Unix
Unix est un systme dexploitation n au dbut des annes 1970, et qui se dcline de nos jours sous diffrentes formes : les versions commerciales dUnix, telles que Solaris (Sun), AIX (IBM), HP-UX, etc. ; les versions libres de systmes clonant Unix (on parle de systmes Unix-like), dont le plus connu est Linux, mais il existe aussi des variantes comme FreeBSD, NetBSD, Hurd, etc. Le shell fait partie intgrante dUnix depuis les dbuts de celui-ci en 1971. On peut dailleurs en trouver une prsentation simplie dans le clbre article [Ritchie 1974] The UNIX Time-Sharing System qui dcrivait les premires versions dUnix. Le shell est avant tout un interprteur de commandes, charg de lire les ordres que lutilisateur saisit au clavier, de les analyser, et dexcuter les commandes correspondantes. Curieusement, les tubes de communication (pipes) permettant de faire circuler des donnes de processus en processus et donnant toute leur puissance aux scripts shell, ne sont apparus que plus tard, n 1972. Le premier shell fut crit par Steve Bourne et le chier excutable correspondant tait traditionnellement /bin/sh. Il permettait dcrire de vritables scripts complets, avec des structures de contrle performantes. Toutefois, son utilisation quotidienne de manire interactive pchait quelque peu par manque dergonomie. Un nouveau shell dit shell C fut donc mis au point par William Joy luniversit de Berkeley. Cet outil fut ofciellement introduit dans certaines distributions Unix en 1978, et prsent dans un article clbre [Joy 1978]. Le chier excutable tait /bin/csh. Linterface utilisateur tait beaucoup plus agrable, grant un historique des commandes, des alias, etc. Il ne faut pas oublier que lutilisateur dUnix ne disposait cette poque que dun terminal en mode texte, et que lintroduction du contrle des jobs, par exemple, augmentait considrablement la productivit en permettant de basculer facilement dapplication en application. Hlas, le shell C fut dot dun langage de programmation ressemblant quelque peu au langage C, mais dont limplmentation tait fortement bogue. En dpit des corrections qui y furent apportes, le comportement des scripts pour shell C ntait pas satisfaisant. La plupart des utilisateurs dcidrent alors demployer le shell C pour laccs interactif au systme, et le shell Bourne pour lcriture de scripts automatiss. On remarquera par exemple que louvrage [Dubois 95] Using csh & tcsh naborde pas la programmation de scripts, car lauteur considre que dautres shells sont plus appropris pour cette tche.

Principes des scripts Shell CHAPITRE 1

En 1983, David G. Korn, travaillant aux laboratoires AT&T Bell, dveloppa un nouveau shell, nomm /bin/ksh (Korn Shell), dont le but tait de proposer la fois les qualits interactives du shell C et lefcacit des scripts du shell Bourne. Linterface utilisateur, par exemple, proposait la fois un mode ddition vi et un mode Emacs. Le shell Korn volua rapidement, et de nouvelles versions en furent proposes en 1986, puis en 1988 et enn en 1993. Le shell Korn tait uniquement disponible comme logiciel commercial, vendu par AT&T, ce que certains utilisateurs lui reprochaient. Cette politique a t modie le 1er mars 2000, puisque le shell Korn 1993 est prsent disponible sous licence open source. Paralllement au dploiement du shell Korn, la ligne des shells C connut un nouveau regain dintrt avec la diffusion de Tcsh, qui essayait de copier certaines fonctionnalits dinterface dun ancien systme dexploitation pour PDP-10, le Tenex. Ce shell offrait galement de nombreuses amliorations et corrections des dfauts de Csh. Nous le retrouvons de nos jours sur les systmes Linux, entre autres. Paralllement lvolution des Unix et des shells commerciaux , on assista dans les annes 1980 la perce des logiciels libres, et plus particulirement du projet GNU (acronyme rcursif de Gnu is Not Unix) destin cloner le systme Unix, ce qui permettra ultrieurement lapparition des distributions GNU/Linux. La FSF (Free Software Fundation), fonde en 1985 par Richard M. Stallman pour dvelopper le projet GNU, avait besoin dun interprteur de commandes libre et performant. Un programmeur, Brian Fox, fut embauch pour dvelopper ce shell. Ce projet, repris par la suite par Chet Ramey, donna naissance au fameux shell Bash, que lon trouve demble sur les distributions Linux. Bash est lacronyme de Bourne Again Shell, revendiquant ainsi sa liation forte au shell sh original. Dun autre ct, Bash a aussi intgr de nombreuses fonctionnalits provenant du shell Korn, et mme de Tcsh. Il est ainsi devenu un outil trs puissant, que la plupart des utilisateurs de Linux emploient comme shell de connexion.
Libre ou ? Faute de mieux, nous opposerons les termes libres et commerciaux concernant les logiciels ou les implmentations dUnix. Cette distinction nest pas parfaite, pour de nombreuses raisons, mais elle est la plus intuitive et la plus proche de la ralit conomique des produits disponibles.

Avant la mise sous licence libre du shell Korn, les systmes libres dsireux de disposer dun shell de ce type utilisaient une variante gratuite : le shell Pdksh (Public Domain Korn Shell) crit lorigine par Eric Gisin. la n des annes 1980, il existait une plthore de systmes compatibles Unix ou prtendument compatibles Unix qui implmentaient chacun de nouvelles fonctionnalits par rapport au systme Unix original. Chaque fabricant de matriel, chaque diteur de logiciels dsirait disposer de sa propre version compatible Unix, et la situation tait devenue catastrophique en ce qui concerne la portabilit des applications.

Shells Linux et Unix par la pratique

Mme les commandes shell simples pouvaient disposer de variantes diffrentes sur chaque systme. Il fut donc ncessaire duniformiser le paysage propos par toutes ces versions dUnix. Une norme fut propose par lIEEE : Posix. Elle dcrivait linterface entre le cur du systme dexploitation (le noyau) et les applications, ainsi que le comportement dun certain nombre doutils systme dont le shell. Ce standard ntait pas consultable librement, et sa redistribution tait prohibe, aussi les utilisateurs lui prfrrent une autre norme, crite par lOpen Group (association dditeurs de systmes compatibles Unix) et sensiblement quivalente : Single Unix Specication (S.U.S). De nos jours, la version 3 de ce standard (abrg habituellement en SUSv3) a intgr le contenu de la norme Posix. Pour sassurer de la portabilit dune fonction propose par le shell ou dune option offerte par une commande, on pourra donc se reporter vers SUSv3, disponible aprs avoir rempli un formulaire rapide sur :
http://www.unix.org/single_unix_specication/.

Pourquoi crire un script shell ?


Lcriture de scripts shell peut rpondre plusieurs besoins diffrents. Je voudrais citer quelques domaines dapplications, pour donner une ide du panorama couvert par les scripts shell usuels : Les plus simples dentre eux serviront simplement lancer quelques commandes disposant doptions complexes, et quon dsire employer rgulirement sans avoir se reporter sans cesse leur documentation (pages de manuel). Les scripts dinitialisation de boot du systme, permettent dnumrer les priphriques disponibles, et de les initialiser, de prparer les systmes de chiers, les partitions, et de congurer les communications et les services rseau. Ils sont normalement livrs avec le systme dexploitation, mais ladministrateur expriment doit parfois intervenir dans ces scripts (souvent longs et fastidieux) pour personnaliser un serveur. Certaines applications spciques (logiciels mtier ) ncessitent un paramtrage complexe, et des scripts shell sont employs pour assurer la gestion de congurations, la prparation des rpertoires et des chiers temporaires, etc. La supervision dun parc informatique rclame des tches automatises pour vrier ltat des systmes (utilisation de la mmoire et des espaces disque, antivirus...) et assurer des tches de maintenance priodique (effacement des chiers temporaires, rotation des chiers de traces...). De nombreux serveurs utilisent une interface constitue de scripts shell pour transmettre des requtes SQL au logiciel de base de donnes (Oracle, MySQL, etc.) Pour assurer lexploitation de serveurs rseau ou de machines industrielles, ladministrateur doit exploiter la sortie de certaines commandes de supervision, et les traces

Principes des scripts Shell CHAPITRE 1

enregistres dans des chiers de journalisation (log le). Pour cela des scripts shell enrichis de commandes Awk permettront dextraire facilement des informations statistiques. Naturellement, il est possible dcrire des scripts shell pour dautres domaines dapplication (traitement automatique du contenu de chiers de texte, calcul, menus dinterface utilisateur, etc.), mais les exemples dutilisation les plus courants relvent plutt de ladministration du systme.

Outils ncessaires
Le but de ce livre est de permettre au lecteur de raliser rapidement des scripts utiles, complets et robustes. Nous ne prtendons ni lexhaustivit dun manuel de rfrence, ni au niveau de dtail quun ouvrage de programmation avance pourrait offrir. Pour les lecteurs dsireux dapprofondir leurs connaissances sur un langage particulier, nous prsenterons des rfrences complmentaires en bibliographie. Nous nous intresserons la programmation de scripts compatibles SUSv3 , autrement dit des scripts utilisables tant sur systmes Unix commerciaux (employant le shell Ksh) que sur les systmes libres comme Linux (avec le shell Bash). Dans certains scripts proposs en exemple ou en exercice, nous devrons faire appel certaines extensions GNU, cest--dire des options dutilitaires systme non documentes dans SUSv3, ajoutes dans leur version GNU. Ce livre se voulant le plus interactif possible, il est important que le lecteur puisse saisir et excuter les exemples proposs, et tlcharger le code source des scripts sur le site personnel de lauteur :
http://www.blaess.fr/christophe

Le lecteur nayant pas daccs immdiat un systme Unix ou compatible, mais disposant dune machine fonctionnant avec Windows, pourra se tourner vers le projet Cygwin (voir http://www.cygwin.com) qui propose un portage des outils GNU (donc de Bash) sur ce systme dexploitation. Un second outil sera indispensable : un diteur de texte pour saisir et modier le contenu des scripts. Il existe une plthore dditeurs sur chaque systme, les plus traditionnels sur Unix tant vi et Emacs (existant dans des versions plus ou moins conviviales et esthtiques), mais on peut galement citer Nedit, Gedit, Kwrite, etc.

Excution dun script


Avant de rentrer dans le dtail des langages de programmation par scripts, consacrons quelques pages aux mthodes possibles pour que soit excut un tel programme. Il nest pas indispensable de comprendre en dtail les interactions entre le shell, le noyau et linterprteur pour faire excuter un script, mais cela permet de bien comprendre le rle de la premire ligne que nous retrouverons dans tous nos chiers.

Shells Linux et Unix par la pratique

tant bien entendu quun script ne peut pas tre excut dune manire autonome, mais quil ncessite la prsence dun interprteur pour le faire fonctionner, plusieurs mthodes peuvent tre invoques pour lancer un tel programme.

Invocation de linterprteur
Pour tester cela, nous allons crer un premier script extrmement simple. Utilisez lditeur de texte de votre choix pour crer un chier nomm essai.sh contenant uniquement la ligne suivante :
essai.sh: echo "Ceci est mon premier essai"

La commande echo du shell doit simplement afcher la chane transmise sur sa ligne de commande. prsent nous allons essayer de lexcuter en le prcdant, sur la ligne de commande, de lappel du shell. Le symbole $, prsent en dbut de ligne ci-dessous, correspond au symbole dinvite (prompt) du shell et ne doit naturellement pas tre saisi :
$ sh essai.sh Ceci est mon premier essai $

Cela fonctionne bien. Remarquez que le sufxe .sh ajout la n du nom du script na aucune importance ; il ne sagit que dune information pour lutilisateur. Le systme Unix ne tient aucun compte des sufxes ventuels des noms de chiers. Par exemple, renommons-le et ressayons :
$ mv essai.sh essai $ sh essai Ceci est mon premier essai $

Les lecteurs les plus intrpides qui se seront aventurs nommer le chier test plutt que essai pourront avoir une mauvaise surprise ici, avec un message derreur du type :
$ sh test /usr/bin/test: /usr/bin/test: cannot execute binary file $

Cela est d la prsence sur leur systme dun autre chier excutable nomm test, mais qui nest pas un script shell. Nous en reparlerons plus loin.

Principes des scripts Shell CHAPITRE 1

Appel direct
Plutt que dinvoquer linterprteur suivi du nom du chier, nous prfrerions appeler directement le script, comme sil sagissait dune commande habituelle. Essayons donc dappeler directement notre script essai:
$ essai ksh: essai: non trouv [Aucun fichier ou rpertoire de ce type]

Le message derreur variera suivant le shell interactif que vous utilisez, mais le contenu sera en substance identique : le shell dplore de ne pouvoir trouver le chier essai. Ceci est tout fait normal. Lorsque nous invoquons une commande Unix (par exemple, ls), le systme devra trouver un chier excutable de ce nom (/bin/ls en loccurrence). Mais il est impossible de parcourir tous les rpertoires de tous les disques pour rechercher ce chier, le temps de rponse serait horriblement long ! Pour amliorer les performances, le systme Unix ne recherche les chiers implmentant les commandes que dans un nombre restreint de rpertoires. Ceux-ci sont numrs dans la variable denvironnement PATH, que nous pouvons consulter. Pour cela, il faudra faire prcder le nom PATH dun caractre $, comme nous le dtaillerons plus tard, et bien respecter le nom en majuscules. Examinons le contenu de la variable :
$ echo $PATH /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/home/cpb/bin $

Nous voyons que plusieurs rpertoires sont indiqus, spars par des deux-points. Le rpertoire dans lequel nous nous trouvons actuellement nest pas mentionn, donc le systme ne vient pas y chercher le chier essai.
Portabilit Nous sommes ici sur un systme Linux, et lemplacement exact des rpertoires peut diffrer suivant les versions dUnix. Toutefois, on retrouvera toujours une organisation approchante.

Pour que le shell puisse trouver notre chier essai, plusieurs possibilits soffrent nous. Il est possible de dplacer le script dans un des rpertoires du PATH. Si nous avions cr un script utile pour tous les utilisateurs dUnix, il serait en effet envisageable de le placer dans /usr/bin. Si ce script sadressait uniquement aux utilisateurs de notre systme, le rpertoire /usr/local/bin serait plus appropri. Enn, si le script tait rserv notre usage personnel, le sous-rpertoire bin/ situ dans notre rpertoire personnel conviendrait mieux. Toutefois ceci suppose que le script soit parfaitement au point. Pendant la priode de cration et de corrections initiales, il vaut mieux le conserver dans un rpertoire de travail spcique.

Shells Linux et Unix par la pratique

Une autre approche consiste modier notre variable PATH pour lui ajouter un point (.) qui reprsente toujours le rpertoire courant. Pour des raisons de scurit, il faut toujours placer ce point en dernire position dans la variable PATH (ceci sera tudi en dtail dans les exercices en n de chapitre). On pourra excuter la commande :
$ PATH=$PATH:.

avant dappeler essai, voire la placer dans le chier dinitialisation (chier profile) de notre shell. Attention toutefois, certains problmes de scurit peuvent se poser si vous travaillez sur un systme ouvert au public. La troisime possibilit la plus contraignante, mais la plus sre consiste indiquer au systme le rpertoire o se trouve le chier chaque fois quon linvoque. Ceci sobtient aisment si le chier se trouve dans le rpertoire courant en utilisant lappel :
$ ./essai

Malheureusement, cela ne fonctionne pas beaucoup mieux :


$ ./essai ksh: ./essai: ne peut pas excuter [Permission non accorde] $

Le script ne sexcute pas plus quavant, mais le message derreur a chang. Ceci est d aux permissions accordes pour ce chier, et que lon peut observer avec loption -l de ls:
$ ls -l essai -rw-r--r-- 1 cpb users 34 sep 3 14:08 essai $

La premire colonne dcrit le type de chier et les permissions concernant sa manipulation : Le premier tiret - correspond un chier normal (d pour un rpertoire, l pour un lien symbolique, etc.). Les trois lettres suivantes indiquent que le propritaire cpb du chier a les droits de lecture (r) et dcriture (w) sur son chier, mais labsence de lettre x, remplace ici par un tiret, signie que le propritaire ne peut pas excuter ce chier. Cest justement notre problme. Les trois lettres r-- suivantes dcrivent les droits fournis aux autres utilisateurs appartenant au mme groupe que le chier (users). Seule la lecture du contenu du chier sera possible. Il ne sera pas possible de lexcuter et de le modier.

Principes des scripts Shell CHAPITRE 1

Enn les trois dernires lettres indiquent les permissions accordes tous les autres utilisateurs du systme (r-- lecture seulement). Le chier ntant pas excutable, il est impossible de le lancer. Pour modier les permissions dun chier, il faut utiliser la commande chmod(1).
Notation Dans toutes les descriptions techniques Unix, une notation comme chmod(1) signie quon fait rfrence un lment nomm chmod dont la documentation se situe dans la section 1 du manuel. On obtiendra donc des informations supplmentaires en appelant la commande man 1 chmod.

Il y a plusieurs types doptions possibles pour chmod, la plus parlante est srement celle-ci :
$ chmod +x essai $ ls -l essai -rwxr-xr-x 1 cpb users 34 sep 3 14:08 essai $

Loption +x de chmod ajoute lautorisation dexcution pour tous les types dutilisateurs. Vrions que notre script fonctionne :
$ ./essai Ceci est mon premier essai $

Parfait !

Ligne shebang
Marquons toutefois une brve pause, et rchissons un instant ce que nous avons ralis. Nous avons cr le chier essai contenant une seule ligne afchant un message ; nous avons rendu le chier excutable avec chmod; puis nous lavons lanc en utilisant la notation ./essai. Nous savons que ce chier doit tre interprt par un shell, cest--dire quun shell disponible sur le systme (sh, bash, ksh...) doit lire le script et traduire son contenu ligne ligne. Mais comment le systme sait-il que ce chier doit tre interprt par un shell ? Comment sait-il quil sagit dun script shell et non dun script Sed, Awk, Perl, ou autre ? Comment se fait lassociation entre notre chier et le shell du systme ? En ralit, nous navons rien prcis, et si le script fonctionne quand mme, cest uniquement grce un comportement par dfaut dans le cas o cette information est absente.

10

Shells Linux et Unix par la pratique

Pour prciser linterprteur employer, on utilise le principe de la ligne shebang (contraction de shell et de bang point dexclamation en anglais). Lorsque le noyau saperoit que les deux premiers caractres du chier sont #!, il considre que tout le reste de la ligne reprsente le nom de linterprteur utiliser. Et si lon demande excuter le chier mon_script.sh crit ainsi :
mon_script.sh : #! /bin/ksh echo "Voici un second script

le systme dexploitation traduira linvocation :


$ ./mon_script.sh

en :
$ /bin/ksh ./mon_script.sh

Le shell considre que lorsquil rencontre un caractre #, il doit ignorer toute la suite de la ligne. Cest ainsi quon ajoute des commentaires dans un script shell. Aussi le shell (ksh ici) qui va lire le script ignorera-t-il cette premire ligne. Attention, les caractres # et ! doivent tre exactement le premier et le second du chier ; il ne doivent tre prcds daucune espace, tabulation ou ligne blanche. Insrer une ligne shebang dans un script shell prsente plusieurs avantages : Mme si un script shell peut fonctionner sans cette ligne, sa prsence est gage de comportement correct, linterprteur sera choisi sans ambigut. En outre, si le script est lanc directement par une application (environnement graphique, par exemple) et non par un shell, seule la prsence de la ligne shebang garantira le bon dmarrage du script. La ligne shebang dun script shell fait gnralement appel /bin/sh comme interprteur, mais un script crit pour le langage Awk pourra utiliser #! /bin/awk -f, par exemple, en incluant une option -f sur la ligne de commande de linterprteur. Si le shell employ pour interprter les scripts est souvent /bin/sh, il arrive, sur certains systmes (Solaris par exemple), que celui-ci soit trop ancien, et que lon prfre invoquer un shell plus rcent par exemple /bin/ksh. Enn, le fait de placer cette premire ligne est dj un lment de documentation du script. La personne qui devra diter ce chier ultrieurement trouvera l une premire indication de son fonctionnement. Personnellement, lorsque je dois crire un script shell, mes doigts saisissent machinalement la ligne #!/bin/sh, pendant que je rchis aux premires lignes que jajouterai en dessous. Nous dvelopperons ce thme concernant la bonne criture dun script dans un chapitre ultrieur.

Principes des scripts Shell CHAPITRE 1

11

Conclusion
Dans ce chapitre, nous avons dress un panorama rapide des shells disponibles sur la majeure partie des systmes Unix et Linux. Nous avons galement vu comment crer, rendre excutable et lancer un script shell. Retenons que la prsence de la premire ligne shebang du script sera un gage de qualit et de portabilit du script.

Exercices
Vous trouverez dans les diffrents chapitres de ce livre quelques exercices destins vous entraner et vous perfectionner dans lcriture des scripts. Les solutions des exercices sont proposes en n de livre, dans lannexe A, et les scripts complets peuvent tre tlchargs sur le site de lauteur, ladresse suivante :
http://www.blaess.fr/christophe

1.1 Prise en main du systme (facile)


Identiez les shells disponibles sur votre systme, et dterminez lidentit du shell /bin/sh. Vriez galement les diteurs de texte proposs par votre environnement de travail, et familiarisez-vous avec celui qui vous semble le plus adapt.

1.2 Utilit de la variable PATH (facile)


Pour prendre conscience de lutilit de la variable PATH, effacez-la ainsi :

$ PATH=
Puis essayez quelques commandes :

$ cd /etc $ ls $ echo "Hello" $ cd /bin $ /bin/ls etc.


Que se passe-t-il ? Pourquoi ? Comment y remdier ?

12

Shells Linux et Unix par la pratique

1.3 Rpertoire courant dans le PATH (plutt facile)


Ajoutez le rpertoire . (point) la n de votre variable PATH de cette manire :

$ PATH=$PATH:.
Essayez prsent de lancer le script essai.sh sans le faire prcder de ./. Cela fonctionne-t-il ?

1.4 Dangers associs au PATH (plutt difcile)


crivez un petit script afchant un simple message, appelez-le ls (noubliez pas de le rendre excutable) et sauvegardez-le dans le rpertoire /tmp. Connectez-vous prsent avec une autre identit sur votre systme. Modiez votre variable

PATH pour quelle contienne le rpertoire courant en premire position. Allez prsent dans le rpertoire /tmp, et afchez la liste des chiers quil contient. Que se passe-t-il ?

2
Programmation Shell
Avant dentamer la programmation sous shell, rappelons la syntaxe lmentaire qui est utilise pour lancer des processus depuis la ligne de commande sous Unix et Linux, puisque nous emploierons galement ces symboles dans les scripts.
Saisie Signication
La commande est excute normalement ; le symbole dinvite sera afch lorsquelle se terminera. La commande est excute en arrire-plan, ce qui signie quelle na en principe pas accs au terminal. Le shell reprend donc la main immdiatement, et af che son symbole dinvite alors que la commande continue sexcuter en tche de fond. Le shell afche une ligne du type

commande commande &

[1] 2496
indiquant le numro du job larrire-plan, suivi du PID (lidentiant de processus). Lorsque la commande se termine, le shell afchera une ligne

[1]+ commande > fichier commande >> fichier commande < fichier cmmnd_1 | cmmnd_2

Done commande

juste avant de proposer un nouveau symbole dinvite. La commande est excute, mais sa sortie standard est dirige vers le chier indiqu. Seule la sortie derreur safche lcran. La sortie standard est ajoute en n de chier sans en craser le contenu. La commande est excute, mais ses informations dentre seront lues depuis le chier qui est indiqu plutt que depuis le ter minal. Les deux commandes sont excutes simultanment, lentre standard de la seconde tant connecte par un tube (pipe) la sortie standard de la premire.

Nous tendrons ces possibilits ultrieurement, mais ces lments seront dj sufsants pour comprendre les premiers scripts tudis.

14

Shells Linux et Unix par la pratique

Premier aperu
Avant dentrer dans le dtail de la syntaxe et des commandes pour le shell, nous allons tout dabord survoler un script complet, an de nous familiariser avec la structure des programmes shell. Ce script, dj assez important pour un premier exemple, est une variation sur un article que javais crit en mai 1996 pour le journal la ligne Linux Gazette, [Blaess 1996].

Premier script, rm_secure


Lide est de remplacer la commande rm normale (suppression de chiers et de rpertoires) par un script qui permette de rcuprer les chiers effacs malencontreusement. Il en existe des implmentations plus performantes, mais lavantage de ce script est dtre assez simple, facile installer et tudier. Nous allons lanalyser en dtail, dans son intgralit. Des numros ont t ajouts en dbut de ligne pour rfrence, mais ils ne font videmment pas partie du script.
rm_secure.sh: 1 sauvegarde_rm=~/.rm_saved/ 2 3 function rm 4 { 5 local opt_force=0 6 local opt_interactive=0 7 local opt_recursive=0 8 local opt_verbose=0 9 local opt_empty=0 10 local opt_list=0 11 local opt_restore=0 12 local opt 13 14 OPTERR=0 15 # Analyse des arguments de la ligne de commande 16 while getopts ":dfirRvels-:" opt ; do 17 case $opt in 18 d ) ;; # ignore 19 f ) opt_force=1 ;; 20 i ) opt_interactive=1 ;; 21 r | R ) opt_recursive=1 ;; 22 e ) opt_empty=1 ;; 23 l ) opt_list=1 ;; 24 s ) opt_restore=1 ;; 25 v ) opt_verbose=1 ;; 26 - ) case $OPTARG in 27 directory ) ;; 28 force) opt_force=1 ;; 29 interactive ) opt_interactive=1 ;; 30 recursive ) opt_recursive=1 ;; 31 verbose ) opt_verbose=1 ;;

Programmation Shell CHAPITRE 2

15

32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

help ) /bin/rm --help echo "rm_secure:" echo " -e --empty vider la corbeille" echo " -l --list voir les fichiers sauvs" echo " -s, --restore rcuprer des fichiers" return 0 ;; version ) /bin/rm --version echo "(rm_secure 1.2)" return 0 ;; empty ) opt_empty=1 ;; list ) opt_list=1 ;; restore ) opt_restore=1 ;; * ) echo "option illgale --$OPTARG" return 1;; esac ;; ? ) echo "option illgale -$OPTARG" return 1;; esac done shift $(($OPTIND - 1)) # Crer ventuellement le rpertoire if [ ! -d "$sauvegarde_rm" ] ; then mkdir "$sauvegarde_rm" fi # Vider la poubelle if [ $opt_empty -ne 0 ] ; then /bin/rm -rf "$sauvegarde_rm" return 0 fi # Liste des fichiers sauvs if [ $opt_list -ne 0 ] ; then ( cd "$sauvegarde_rm" ls -lRa * ) fi # Rcupration de fichiers if [ $opt_restore -ne 0 ] ; then while [ -n "$1" ] ; do mv "${sauvegarde_rm}/$1" . shift done return fi # Suppression de fichiers while [ -n "$1" ] ; do # Suppression interactive : interroger l'utilisateur if [ $opt_force -ne 1 ] && [ $opt_interactive -ne 0 ] then

16

Shells Linux et Unix par la pratique

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105

local reponse echo -n "Dtruire $1 ? " read reponse if [ "$reponse" != "y" ] && [ "$reponse" != "Y" ] && [ "$reponse" != "o" ] && [ "$reponse" != "O" ] ; then shift continue fi fi if [ -d "$1" ] && [ $opt_recursive -eq 0 ] ; then # Les rpertoires ncessitent l'option rcursive shift continue fi if [ $opt_verbose -ne 0 ] ; then echo "Suppression $1" fi mv -f "$1" "${sauvegarde_rm}/" shift done } trap "/bin/rm -rf $sauvegarde_rm" EXIT

Lanalyse dtaille du script est un peu laborieuse, mais il est nettement prfrable dtudier un programme rel, complet et utilisable, quun exemple simpliste, construit uniquement pour illustrer les possibilits du shell. Si certaines parties semblent obscures, il ny a pas lieu de sinquiter : nous y reviendrons dans la prsentation des commandes et de la syntaxe.

Analyse dtaille
Exceptionnellement, ce script ne dbute pas par une ligne shebang introduite par #!. En effet, il nest pas conu pour tre excut dans un processus indpendant avec une commande comme ./rm_secure.sh, mais pour tre lu par une commande source ou . depuis un chier dinitialisation du shell (comme ~/.profile ou ~/.bashrc). Lorsquun script est appel ainsi . script, il est interprt directement par le shell en cours dexcution, sans lancer de nouvelle instance. Aussi les variables et fonctions dnies par ce script seront-elles disponibles mme aprs sa terminaison. Il ne faut pas confondre le point utilis ici pour sourcer un script, et le point reprsentant le rpertoire courant dans lappel ./script. Ce script dnit une fonction nomme rm, stendant des lignes 3 103. Le programme tant lu et interprt directement par le shell, et non par un processus ls, cette fonction sera disponible dans lenvironnement du shell qui vient de sinitialiser. Lorsquon appelle, depuis la ligne de saisie, un nom de commande comme rm, le shell va dabord contrler sil existe dans son environnement une fonction qui a ce nom. Sil nen

Programmation Shell CHAPITRE 2

17

trouve pas, il vriera sil sagit de lune de ses commandes internes (comme cd). En dernier ressort, il parcourra lensemble des rpertoires mentionns dans la variable denvironnement PATH la recherche dun chier excutable qui ait le nom indiqu. Naturellement, si la commande contient un chemin daccs (par exemple /bin/rm), il vite tout ce mcanisme et lance directement le chier dsir. Dans notre cas, nous dnissons une fonction ayant pour nom rm, qui sera donc invoque la place du chier /bin/rm habituel. La premire ligne du script dnit une variable qui contient le nom dun rpertoire vers lequel les chiers seront dplacs plutt que vritablement effacs. Des options la ligne de commande nous permettront dexaminer le contenu du rpertoire et de rcuprer des chiers. Il est vident que lutilisateur doit avoir un droit dcriture sur ce rpertoire. La conguration la plus intuitive consiste utiliser un rpertoire qui gure dj dans le rpertoire personnel de lutilisateur et de lui donner un nom qui commence par un point an dviter de le retrouver chaque invocation de ls. Nous voyons que laffectation dune variable se fait, avec le shell, simplement laide dune commande nom=valeur. Il est important de noter quil ne doit pas y avoir despace autour du signe gal. Vient ensuite la dclaration de la fonction. On emploie le mot-cl function suivi du nom de la routine, puis de son corps encadr par des accolades. Les lignes 5 12 dclarent des variables locales de la fonction. Ces variables serviront noter, lors de lanalyse de la ligne de commande, les diffrentes options indiques par lutilisateur. Les dclarations sont ici prcdes du mot-cl local, qui permet de ne les rendre visibles que dans notre fonction. Quand une fonction est, comme ici, destine rester durablement dans la mmoire du shell, il est important de ne pas polluer lenvironnement en laissant inutilement des variables apparatre en dehors de la routine o elles sont employes. Notre fonction rm accepte les mmes options la ligne de commande que la commande / bin/rm, et en ajoute trois nouvelles :
Option Signication
Ignore Ne pas interroger lutilisateur Interroger lutilisateur avant de supprimer un chier Supprimer dune faon rcursive les rpertoires Afcher les noms des chiers avant de les supprimer Afcher une page daide Afcher le numro de version du programme Vider la corbeille de sauvegarde Voir le contenu de la corbeille Rcuprer un chier dans la corbeille

Provenance

-d ou --directory -f ou --force -i ou --interactive -r, -R, ou --recursive -v ou --verbose --help --version -e ou --empty -l ou --list -s ou --restore

/bin/rm /bin/rm /bin/rm /bin/rm /bin/rm /bin/rm /bin/rm rm_secure rm_secure rm_secure

18

Shells Linux et Unix par la pratique

Avant de lire les arguments de la ligne de commande, nous devons initialiser une variable systme nomme OPTIND, ce qui a lieu la ligne 14. Le rle de cette variable sera dtaill plus bas. La ligne 16 introduit une boucle while. Voici la syntaxe de ce type de boucle :
while condition do corps de la boucle... done

Les instructions contenues dans le corps de la boucle sont rptes tant que la condition indique est vraie. An de rendre lcriture un peu plus compacte, il est frquent de remplacer le saut de ligne qui se trouve aprs la condition par un caractre point-virgule, qui, en programmation shell, signie n dinstruction . La boucle est donc gnralement crite ainsi :
while condition ; do corps de la boucle done

Une erreur frquente chez les dbutants est de tenter de placer le point-virgule l o il parat visuellement le plus naturel pour un programmeur C ou Pascal : en n de ligne. Sa position correcte est pourtant bien avant le do! Ici, la condition est exprime par une commande interne du shell :
getopts ":dfirRvels-:" opt

La commande getopts permet danalyser les arguments qui se trouvent sur la ligne de commande. Nous lui fournissons une chane de caractres qui contient les lettres attribues aux options (comme dans le tableau de la page prcdente), et le nom dune variable quelle devra remplir. Cette fonction renvoie une valeur vraie tant quelle trouve une option qui est contenue dans la liste, et place le caractre dans la variable opt. Nous dtaillerons ultrieurement le fonctionnement de cette routine, et la signication des deux-points prsents dans cette chane. Retenons simplement quarrivs la ligne 17, nous savons que lun des caractres inscrits dans la chane est prsent dans la variable opt. Sur cette ligne 17, nous trouvons une structure de slection case qui va nous permettre de distribuer le contrle du programme en fonction du contenu dune variable. Sa syntaxe est la suivante :
case expression in valeur_1 ) action_1 ;; valeur_2 ) action_2 ;; esac

La valeur renvoye par lexpression, ici le contenu de la variable opt, est compare successivement aux valeurs proposes jusqu ce quil en soit trouv une qui lui corresponde, et laction associe est alors excute. Les diffrents cas sont spars par deux

Programmation Shell CHAPITRE 2

19

points-virgules. Pour le dernier cas, toutefois, ce signe de ponctuation est facultatif, mais il est recommand de lemployer quand mme, an dviter des problmes ultrieurs si un nouveau cas doit tre ajout. Le mot-cl esac (case lenvers), que lon trouve la ligne 49, sert marquer la n de cette structure de slection. Nous remarquons au passage que, ligne 17, nous lisons pour la premire fois le contenu dune variable. Cela seffectue en prxant son nom par un caractre $. Ainsi, nous nous rfrons au contenu de la variable opt en crivant $opt. Il faut bien comprendre ds prsent que le nom rel de la variable est bien opt; le $ est un oprateur demandant daccder son contenu. Nous verrons plus avant des formes plus gnrales de cet oprateur. Les valeurs auxquelles lexpression est compare dans les diffrentes branches de la structure case ne sont pas limites de simples valeurs entires, comme peuvent y tre habitus les utilisateurs du langage C, mais peuvent reprsenter des chanes de caractres compltes, incorporant des caractres gnriques comme lastrisque *. Ici, nous nous limiterons employer le caractre | qui reprsente un OU logique, la ligne 21. Ainsi laction
opt_recursive=1

est excute si le caractre contenu dans opt est un r ou un R. Nous observons que les lignes 19 25 servent remplir les variables qui reprsentent les options en fonction des caractres rencontrs par getopts. Un cas intressant se prsente la ligne 26, puisque nous avons rencontr un caractre doption constitu par un tiret. Cela signie que loption commence par --. Dans la chane de caractres que nous avons transmise getopts la ligne 16, nous avons fait suivre le tiret dun deux-points. Cela indique la commande getopts que nous attendons un argument loption -. Il sagit dune astuce pour traiter les options longues comme --help. Lorsque getopts rencontre loption -, il place largument qui la suit (par exemple help) dans la variable OPTARG. Nous pouvons donc reprendre une structure de slection case pour examiner le contenu de cette variable, ce qui est fait de la ligne 26 la ligne 46 o se trouve le esac correspondant. Les options longues, directory, force, interactive, verbose, recursive, empty, list, restore, sont traites comme leurs homologues courtes, dcrites dans le tableau prcdent. Aussi les options help et version commencent par invoquer la vritable commande rm an quelle afche ses propres informations, avant dcrire leurs messages laide de la commande echo. On remarquera que ces deux cas se terminent par une commande return0, ce qui provoque la n de la fonction rm, et renvoie un code de retour nul indiquant que tout sest bien pass. Le motif mis en comparaison sur la ligne 44, un simple astrisque, peut correspondre nimporte quelle chane de caractres selon les critres du shell. Ce motif sert donc absorber toutes les saisies non valides dans les options longues. Nous afchons dans ce cas un message derreur au moyen de la commande :
echo "option illgale --$OPTARG"

20

Shells Linux et Unix par la pratique

Nous remarquons quau sein de la chane de caractres encadre par des guillemets, loprateur $ agit toujours, et remplace OPTARG par son contenu, cest--dire le texte de loption longue errone. En ce cas, nous invoquons la commande return avec un argument 1, ce qui signie conventionnellement quune erreur sest produite. la ligne 47, nous revenons la premire structure de distribution case-esac. Nous avions mis la ligne 16 un deux-points en premire position de la chane doptions transmise getopts. Cela permet de congurer le comportement de cette commande lorsquelle rencontre une lettre doption non valide. Dans ce cas, elle met le caractre ? dans la variable indique, opt en loccurrence, stocke la lettre non valide dans la variable OPTARG, et nafche aucun message derreur. Notre dernier cas, sur la ligne 47, sert donc afcher un message derreur, et arrter la fonction avec un code dchec. Aprs clture avec esac et done de la lecture des options, il nous reste traiter les autres arguments qui se trouvent sur la ligne de commande, ceux qui ne sont pas des options, cest--dire les noms de chiers ou de rpertoires quil faut supprimer ou rcuprer. Lorsquun script (ou une fonction) est invoqu, les arguments lui sont transmis dans des variables spciales. Ces variables, accessibles uniquement en lecture, sont reprsentes par le numro de largument sur la ligne de commande. Ainsi, $1 renvoie la valeur du premier argument, $2 celle du deuxime, et ainsi de suite. Nous verrons ultrieurement quil existe aussi des variables spciales, accessibles avec $0, $#, $* et $@, qui aident manipuler ces arguments. La boucle while autour de getopts a parcouru toutes les options qui se trouvent en dbut de ligne de commande, puis elle se termine ds que getopts rencontre un argument qui ne commence pas par un tiret. Avant danalyser un argument, getopts stocke son indice dans la variable OPTIND, que nous avions initialise zro la ligne 14. Si les n premiers arguments de la ligne de commande sont des options valides, en sortie de la boucle while, la variable OPTIND vaut n+1. Il est donc prsent ncessaire de sauter les OPTIND-1 premiers arguments pour passer aux noms de chiers ou de rpertoires. Cest ce qui est ralis sur la ligne 51 :
shift $(($OPTIND - 1))

La construction $(()) encadre une expression arithmtique que le shell doit interprter. Ici, il sagit donc simplement du nombre darguments liminer, soit $OPTIND-1. La commande shiftn dcale les arguments en supprimant les n premiers. Nous allons entrer prsent dans le vif du sujet, avec les fonctionnalits vritables du script. Toutefois, nous allons auparavant crer le rpertoire de sauvegarde, sil nexiste pas encore. Cela nous vitera de devoir vrier sa prsence plusieurs reprises dans le reste du programme. Nous voyons donc apparatre sur les lignes 54 56 une nouvelle structure de contrle, le test if-then-else. Sa syntaxe est la suivante :
if condition then action else autre action fi

Programmation Shell CHAPITRE 2

21

On regroupe souvent les deux premires lignes en une seule, en sparant les deux commandes par un point-virgule, comme ceci :
if condition ; then action else autre action fi

Si la condition est value une valeur vraie, la premire action est excute. Sinon, le contrle passe la seconde partie, aprs le else. Dans notre cas, la condition est reprsente par une expression a priori surprenante :
[ ! -d "$sauvegarde_rm" ]

En fait, la construction [] reprsente un test. Il existe dailleurs un synonyme de cette construction qui scrit test. On pourrait donc formuler notre expression comme ceci :
test!d"$sauvegarde_rm"

Lemploi des crochets est plus rpandu, peut-tre par souci de concision. On notera quil ne sagit pas simplement de caractres dencadrement comme () ou {}, mais bien dune construction logique complte, chaque caractre tant indpendant. Cela signie que les deux crochets doivent tre entours despaces. Il ne faut pas essayer de les coller leur contenu comme on pourrait avoir tendance le faire : [!d"$sauvegarde_rm"], le shell ne le permet pas. Loption d du test permet de vrier si le nom fourni en argument est bien un rpertoire existant. Le point dexclamation qui se trouve au dbut sert inverser le rsultat du test. Les lignes 54 56 peuvent donc sexprimer ainsi : si le rpertoire $sauvegarde_rm nexiste pas, alors
mkdir "$sauvegarde_rm"

n On pourrait amliorer cette cration de deux manires : Il serait bon de vrier si mkdir a russi la cration. Sil existe dj un chier normal qui a le nom prvu pour le rpertoire de sauvegarde, ou si lutilisateur na pas de droit dcriture sur le rpertoire parent, cette commande peut en effet chouer. Il faudrait alors en tenir compte pour son comportement ultrieur. Pour garder une certaine condentialit aux chiers supprims, il serait bon de protger le rpertoire contre les accs des autres utilisateurs. On pourrait prvoir une commande chmod700"$sauvegarde_rm" aprs la cration. Nous pouvons commencer prsent faire agir le script en fonction des options rclames. Tout dabord, les lignes 59 62 grent loption de vidage de la corbeille :
59 60 61 62 if [ $opt_empty -ne 0 ] ; then /bin/rm -rf "$sauvegarde_rm" return 0 fi

22

Shells Linux et Unix par la pratique

Le test [ xxx -ne yyy ] est une comparaison arithmtique entre xxx et yyy. Il renvoie une valeur vraie si les deux lments sont diffrents (not equal). En dautres termes, ces lignes signient : si la variable reprsentant loption empty nest pas nulle, alors effacer le rpertoire de sauvegarde quitter la fonction en indiquant une russite n La deuxime option que nous traitons est la demande dafchage du contenu de la corbeille. Pour ce faire, nous employons les lignes 66 et 67 :
( cd "$sauvegarde_rm" ls -lRa * )

Elles correspondent un dplacement vers le rpertoire de sauvegarde, et un afchage rcursif de son contenu et de celui des sous-rpertoires. Nous avons encadr ces deux lignes par des parenthses. On demande ainsi au shell de les excuter dans un sous-shell indpendant, ce qui prsente lavantage de ne pas modier le rpertoire de travail du shell principal. Un nouveau processus est donc cr, et seul le rpertoire de travail de ce processus est modi. Loption traite ensuite est la rcupration de chiers ou de rpertoires effacs. Une boucle stend des lignes 72 75 :
while [ -n "$1" ] ; do mv "${sauvegarde_rm}/$1" . shift done

Le test [ -n "$1" ] vrie si la longueur de la chane "$1" est non nulle. Ce test russit donc tant quil reste au moins un argument traiter. Linstruction shift que lon trouve sur la ligne 74 sert liminer largument que lon vient de traiter. Cette boucle permet ainsi de balayer tous les noms de chiers ou de rpertoires un par un. La ligne 73 essaie de rcuprer dans le rpertoire sauvegarde_rm un ventuel chier sauvegard, en le dplaant vers le rpertoire courant. Remarquons quil est tout fait possible, avec cette mme commande, de rcuprer des arborescences compltes, y compris les sous-rpertoires. La portion du programme qui soccupe des suppressions se situe entre les lignes 80 et 102. Elle est construite au sein dune boucle while-do qui balaie les noms des chiers et des rpertoires indiqus sur la ligne de commande. Une premire tape consiste interroger lutilisateur si loption interactive a t rclame, et si loption force ne la pas t. Nous voyons au passage comment deux conditions peuvent tre regroupes par un ET logique au sein dun test. Nous reviendrons sur ce mcanisme ultrieurement. Aprs dclaration dune variable locale, et afchage dun message dinterrogation (avec loption -n de echo pour viter daller la ligne), le script invoque la commande shell read. Cette dernire lit une ligne et la place dans la variable indique en argument.

Programmation Shell CHAPITRE 2

23

Nous comparons alors cette rponse avec quatre chanes possibles : y, Y, o, et O. Loprateur de comparaison des chanes est =, et son inverse est !=. Si la rponse ne correspond aucune de ces possibilits, nous invoquons dabord shift, pour dcaler les arguments, puis la commande continue qui nous permet de revenir au dbut de la boucle while. Les arguments pour lesquels lutilisateur na pas rpondu par lafrmative sont ainsi oublis . La deuxime vrication concerne les rpertoires. Ceux-ci, en effet, ne peuvent tre effacs quavec loption recursive. Si le test de la ligne 92 choue, largument est ignor et on passe au suivant. Si on a rclam un comportement volubile du programme (option -v ou --verbose), on afche le nom du chier ou du rpertoire trait avant de poursuivre. Puis, les lignes 100 et 101 dplacent effectivement le chier, et passent largument suivant. La fonction qui remplace la commande rm est donc prsent crite. Lorsque le script est excut laide de linstruction source ou de . , il place cette fonction dans la mmoire du shell ; elle est prte tre invoque la place de rm. Il demeure toutefois une dernire ligne apparemment mystrieuse :
trap "/bin/rm -rf $sauvegarde_rm" EXIT

Cette ligne est excute directement par le shell lorsque nous appelons notre script. La commande trap permet dassurer une gestion minimale des signaux. Voici sa syntaxe gnrale :
trap commande signal

Lorsque le shell recevra le signal indiqu, il droutera son excution pour raliser immdiatement la commande passe en argument. Les signaux permettent une communication entre processus, et surtout une prise en compte asynchrone des vnements quun logiciel peut tre amen rencontrer (interruption dune ligne de communication, n dune temporisation, dpassement dune limite doccupation du disque, etc.). Nous reviendrons plus en dtail sur la gestion des signaux ultrieurement. Ici, la commande trap nous sert intercepter un pseudo-signal simul par le shell. En effet, avant que le shell ne se termine, il simule lmission dun signal nomm EXIT. La commande passe en argument est donc excute juste avant de refermer une session de travail. Elle sert effacer le rpertoire de sauvegarde. Cette ligne est bien entendu optionnelle, mais je conseille de la conserver, car elle vite lutilisateur de devoir vider explicitement sa corbeille (manuellement ou dune manire programme laide de la crontab).

Performances
Lutilitaire rm_secure na pas pour objet de fournir une rcupration des chiers sur du long terme. Ce rle est dvolu aux mcanismes de sauvegarde priodique du systme. En fait, rm_secure est conu comme une sorte de commande Contrle-Z ou dition/Annuler qui permet de corriger immdiatement une erreur de frappe ayant entran la destruction

24

Shells Linux et Unix par la pratique

involontaire dun chier. En tenant compte de ses spcicits, chacun est toujours libre de modier le script sa convenance pour ladapter un cas particulier. Il faut aussi tre conscient que, dans un environnement graphique X Window, o plusieurs fentres xterm sont utilises simultanment, ds que lon ferme lune dentre elles, la commande deffacement est invoque, dtruisant le rpertoire de sauvegarde commun toutes les fentres. Si ce point pose un problme, il est malgr tout possible de crer un rpertoire de sauvegarde pour chaque instance indpendante du shell. On obtient cela en ajoutant au nom du rpertoire un sufxe qui reprsente le PID du shell, en remplaant la premire ligne du script par :
sauvegarde_rm=~/.rm_saved_$$/

Un tel script a pour vocation de rester le plus discret possible vis--vis de lutilisateur. Idalement, on ne devrait se souvenir de sa prsence que le jour o un chier vient dtre effac maladroitement. Il faut donc viter de ralentir le fonctionnement de la commande rm originale. cet effet, nous avons fait le choix de dplacer les chiers avec mv plutt que de les copier avec cp, ou des les archiver avec tar. Lorsque le chier quil faut supprimer se trouve sur le mme systme de chiers que le rpertoire de sauvegarde, la commande mv agit instantanment, car elle ne doit modier que le nom du chier dans larborescence, et pas son contenu. Il sagit du cas le plus courant pour les utilisateurs normaux, dont les rpertoires personnels et tous les descendants se trouvent gnralement sur la mme partition. Pour root, en revanche, la situation est autre, car ce dernier doit souvent intervenir sur des rpertoires qui se trouvent sur des partitions diffrentes. La commande mv ne peut plus simplement dplacer le chier, mais est oblige de le copier, puis de supprimer loriginal. Dans ce cas, notre script induit un ralentissement sensible, notamment lorsquon agit sur des hirarchies compltes (par exemple, en supprimant toute larborescence des sources dun programme) ou sur des chiers volumineux (core, par exemple). On peut choisir de dsactiver lemploi de rm_secure pour root, en partant du principe que toute action entreprise sous ce compte est potentiellement dangereuse, et ncessite une attention accrue tout moment. Idalement, la connexion root sur un systme Unix ou Linux qui compte peu dutilisateurs devrait tre rserve linstallation ou la suppression de paquetages logiciels, lajout dutilisateur et ldition de chiers de conguration (connexion distante, adressage rseau). Dans tous ces cas, on nintervient que sur des chiers dont une copie existe ailleurs (CD, bande de sauvegarde, Internet). Thoriquement, root ne devrait jamais se dplacer et encore moins toucher aux chiers dans les rpertoires personnels des utilisateurs o lon trouve des donnes nexistant quen un seul exemplaire (chiers source, textes, e-mail). Thoriquement

Exemple dexcution
Voici, en conclusion de ce survol dun script pour shell, un exemple dutilisation. Nous supposons que les chiers dinitialisation (~/.profile ou ~/.bashrc), excuts lors du dmarrage des sessions interactives du shell, contiennent une ligne qui permette dinvoquer le script, la manire de .~/bin/rm_secure.sh. Nous considrons donc que la

Programmation Shell CHAPITRE 2

25

fonction rm dnie prcdemment est prsente dans lenvironnement, et a prsance sur le chier excutable /bin/rm.
$ ls rm_secure.sh rm_secure.sh.bak

Je souhaite ne supprimer que le chier de sauvegarde, et jappelle donc rm*.bak pour viter de saisir son nom en entier. Hlas, jintroduis par erreur un caractre espace aprs lastrisque.
$ rm * .bak mv:.bak.: Aucun fichier ou rpertoire de ce type $ ls $

Ae ! Il ne me reste plus qu compter sur laide de notre script. Commenons par nous remmorer ses options :
$ rm --help Usage: /bin/rm [OPTION]... FICHIER... Enlever (unlink) les FICHIER(s). enlever le rpertoire, mme si non vide (usager root seulement) -f, --force ignorer les fichiers inexistants, ne pas demander de confirmation -i, --interactive demander confirmation avant destruction -r, -R, --recursive enlever les rpertoires rcursivement -v, --verbose en mode bavard expliquer ce qui est fait --help afficher l'aide-mmoire --version afficher le nom et la version du logiciel Rapporter toutes anomalies <bug-fileutils@gnu.org>. rm_secure: -e --empty vider la corbeille -l --list voir les fichiers sauvs -s, --restore rcuprer des fichiers $ rm -l -rwxr-xr-x cpb/cpb 2266 2007-09-01 19:39:14 rm_secure.sh -rwxr-xr-x cpb/cpb 2266 2007-09-01 19:39:14 rm_secure.sh.bak $ rm --restore rm_secure.sh $ ls rm_secure.sh -d, --directory

26

Shells Linux et Unix par la pratique

Ouf ! Le chier est rcupr. Vrions maintenant que le rpertoire de sauvegarde est bien vid automatiquement lors de la dconnexion :
$ rm --list -rwxr-xr-x cpb/cpb $ exit 2266 2007-09-01 19:39:14 rm_secure.sh.bak

Essayons de voir sil reste des chiers aprs une nouvelle connexion :
$ rm --list ls: *: Aucun fichier ou rpertoire de ce type $

Ce dernier message nest peut-tre pas trs heureux. Nous laisserons au lecteur le soin dencadrer lappel ls de la ligne 67 par un test if-then-fi qui afche un message plus appropri si la commande ls signale une erreur (ne pas oublier de rediriger la sortie derreur standard de cette dernire commande vers /dev/null pour la dissimuler).

Conclusion
Avec ce chapitre dintroduction la programmation shell, nous avons pu observer la structure des scripts, que nous pourrons tudier plus en dtail par la suite. On peut remarquer la concision de ce langage, qui permet de raliser une tche dj respectable en une centaine de lignes de programmation. Et ce, en raison du niveau dabstraction lev des commandes employes. Par exemple, la suppression rcursive dun rpertoire, comme nous lavons vu dans la dernire ligne du script, demanderait au moins une vingtaine de lignes de programmation en C. Les chapitres venir vont nous permettre dtudier la programmation shell dune manire plus formelle, et plus complte.

Exercices
2.1 Appel dun script par source (facile)
Crez un script qui initialise une nouvelle variable avec une valeur arbitraire, puis afche cette variable. Lancez le script (aprs lavoir rendu excutable avec chmod) comme nous lavons fait dans le chapitre 1. Une fois lexcution termine, essayez dafcher le contenu de la variable :

initialise_et_affiche.sh #! /bin/sh

Programmation Shell CHAPITRE 2

27

ma_variable=2007 echo $ma_variable $ chmod 755 initialise_et_affiche.sh $ ./initialise_et_affiche.sh 2007 $ echo $ma_variable
Rsultat ? Essayez prsent dexcuter le script avec linvocation suivante :

$ . initialise_et_affiche.sh
Et essayez ensuite de consulter le contenu de la variable. N. B. : vous pouvez remplacer le . de linvocation par le mot-cl source si vous travaillez avec bash.

2.2 Utilisation de rm_secure.sh (plutt facile)


ditez le chier de personnalisation de votre shell de connexion (.bashrc, .profile...) et insrez-y la ligne :

. ~/bin/rm_secure.sh
Prenez soin de placer le script rm_secure.sh dans le sous-rpertoire bin/ de votre rpertoire personnel. Reconnectez-vous, et essayez prsent dappeler :

$ rm -v
pour vrier si le script est bien appel la place de la commande rm habituelle. Effectuez quelques essais deffacement et de rcupration de chiers.

2.3 Adaptation de rm_secure.sh (plutt difcile)


En examinant le contenu du script rm_secure.sh, essayez dy apporter quelques modications (par exemple modiez le rpertoire de sauvegarde) et amliorations (par exemple la condentialit sera augmente si le script retire aux chiers sauvegards tous les droits daccs, hormis leur propritaire).

3
valuation dexpressions
Une part importante de la programmation de scripts repose sur une bonne comprhension des mcanismes qui permettent au shell dvaluer correctement les expressions reues. Nous allons donc commencer par tudier lutilisation des variables, avant dobserver en dtail les valuations dexpressions. Une fois que cela aura t effectu, ltude des structures de contrle et des fonctions internes du shell sera plus facile, et nous serons mme de raliser de vritables scripts.

Variables
La programmation sous shell ncessite naturellement des variables pour stocker des informations temporaires, accder des paramtres, etc. Par dfaut, les variables utilises dans les scripts shell ne sont pas types. Le contenu dune variable est considr comme une chane de caractres, sauf si on indique explicitement quelle doit tre traite comme une variable entire qui peut tre utilise dans des calculs arithmtiques. De plus, le shell ne permet pas de manipuler directement de donnes en virgule ottante (sauf les versions rcentes de Ksh, mais sans garantie de portabilit). Nous verrons plus avant comment il convient demployer lutilitaire bc au sein de scripts shell pour raliser des oprations sur des nombres rels. la diffrence des langages compils habituels, une variable na pas tre dclare explicitement. Ds quon lui affecte une valeur, elle commence exister. Cette affectation prend la forme variable=valeur sans espaces autour du signe gal. Le message

30

Shells Linux et Unix par la pratique

derreur i: command not found est peut-tre le plus clbre parmi les utilisateurs du shell :
$ i = 1 bash: i: command not found $

cause des espaces autour du signe gal, Bash a cru que lon essayait dinvoquer la commande i en lui transmettant les arguments = et 1. La bonne syntaxe est la suivante :
$ i=1 $

Pour accder au contenu dune variable, il suft de prxer son nom avec le caractre $. Il ne faut pas confondre ce prxe des variables avec le symbole dinvite du shell, qui est gnralement le mme caractre $. La commande echo afche simplement le contenu de sa ligne de commande :
$ echo $i 1 $

Le nom attribu une variable peut contenir des lettres, des chiffres, ou le caractre soulign _. Il ne doit toutefois pas commencer par un chiffre. Voyons quelques exemples :
$ variable=12 $ echo $variable 12 $

Il faut bien comprendre que le shell a remplac la chane $variable par sa valeur, 12, avant dappeler la commande echo. Cette dernire a donc t invoque par la ligne de commande echo12.
$ variable=abc def bash: def: command not found $

valuation dexpressions CHAPITRE 3

31

Ici, le shell na pas pu interprter correctement cette ligne, car il a cru quelle se composait dune affectation variable=abc, suivie dune commande nomme def (syntaxe rarement utilise mais autorise). Il faut lui indiquer que les mots droite du signe gal forment une seule chane de caractres. On emploie pour cela les guillemets droits :
$ variable="abc def" $ echo $variable abc def $

Nous pouvons vrier quune variable qui na jamais t affecte est considre comme une chane vide :
$ echo $inexistante $

Une variable laquelle on affecte une chane vide existe quand mme. La diffrence entre une variable inexistante et une variable vide peut tre mise en vidence laide de certaines options des constructions de test, ou par lintermdiaire dune conguration particulire du shell qui dclenchera une erreur si on essaie de lire le contenu dune variable inexistante. Cette conguration sobtient au moyen de la commande interne set (que nous dtaillerons dans le chapitre 5) et de son option -u.
$ set -u $ echo $inexistante bash: inexistante: unbound variable $ vide= $ echo $vide $

Prcisions sur loprateur $


On consulte le contenu dune variable laide de loprateur $. Toutefois, la forme $variable, mme si elle est la plus courante, est loin dtre la seule, et loprateur $ propose des fonctionnalits dune richesse surprenante.

32

Shells Linux et Unix par la pratique

Dlimitation du nom de variable

Tout dabord, la construction ${variable} est une gnralisation de $variable, qui permet de dlimiter prcisment le nom de la variable, dans le cas o on souhaiterait le coller un autre lment. Par exemple, supposons que nous souhaitions classer les chiers source dune bibliothque de fonctions, en leur ajoutant un prxe qui corresponde au projet auquel ils appartiennent. Nous pourrions obtenir une squence du type :
$ PREFIXE=projet1 $ FICHIER=source1.c $ NOUVEAU_FICHIER=$PREFIXE_$FICHIER

On espre obtenir projet1_source1.c dans la variable NOUVEAU_FICHIER, mais malheureusement ce nest pas le cas :
$ echo $NOUVEAU_FICHIER source1.c $

Le fait davoir accol directement $PREFIXE, le caractre soulign et $FICHIER ne fonctionne pas comme nous lattendions : le shell a bien remarqu quil y a deux oprateurs $ agissant chacun sur un nom de variable, mais seul le second a t correctement remplac. En effet, le caractre soulign que nous avons ajout comme sparateur entre le prxe et le nom du chier a t considr comme appartenant au nom de la premire variable. Ainsi le shell a-t-il recherch une variable nomme PREFIXE_ et nen a videmment pas trouv. La raison en est que le caractre soulign nest pas un caractre sparateur pour le shell, et quon peut le rencontrer dans les noms de variables. Si nous avions utilis un caractre interdit dans ces noms, nous naurions pas eu le mme problme, car le premier nom de variable aurait t clairement dlimit :
$ echo $PREFIXE.$FICHIER projet1.source1.c $ echo $PREFIXE@$FICHIER projet1@source1.c $ echo $PREFIXE-$FICHIER projet1-source1.c $

valuation dexpressions CHAPITRE 3

33

On pourrait mme utiliser les guillemets droits pour encadrer le caractre soulign an de le sparer du nom de la variable. Ce mcanisme sera dtaill plus avant, lorsque nous tudierons les mthodes de protection des caractres spciaux.
$ echo $PREFIXE"_"$FICHIER projet1_source1.c $

Quoi quil en soit, il arrive que lon doive ajouter des caractres la n dun nom de variable, et loprateur ${} est alors utilis la place du simple $ pour marquer les limites. Ainsi, on peut utiliser :
$ echo ${PREFIXE}_$FICHIER projet1_source1.c $

ou des constructions comme :


$ singulier=mot $ pluriel=${singulier}s $ echo $pluriel mots $

Extraction de sous-chanes et recherche de motifs

Les shells rcents offrent une possibilit dextraction automatique de sous-chanes de caractres au sein dune variable. La version la plus simple sappuie sur loption : de loprateur ${}. Ainsi lexpression ${variable:debut:longueur} est-elle automatiquement remplace par la sous-chane qui commence lemplacement indiqu en seconde position, et qui contient le nombre de caractres indiqu en dernire position. La numrotation des caractres commence zro. Si la longueur nest pas mentionne, on extrait la sous-chane qui stend jusqu la n de la variable. En voici quelques exemples :
$ variable=ABCDEFGHIJKLMNOPQRSTUVWXYZ $ echo ${variable:5:2} FG $ echo ${variable:20} UVWXYZ $

34

Shells Linux et Unix par la pratique

Cette extraction nest pas dcrite dans les spcications Single Unix version 3, et on lui accordera donc une conance limite en ce qui concerne la portabilit. Ce mcanisme fonctionne sans surprise, mais nest pas aussi utile, dans le cadre de la programmation shell, que lon pourrait le croire au premier abord. Dans nos scripts, nous manipulons en effet souvent des noms de chiers ou des adresses rseau, et une autre possibilit dextraction de sous-chane se rvle en gnral plus adapte : elle repose sur lemploi de motifs qui sont construits de la mme manire que lors des recherches de noms de chiers en ligne de commande. Ces motifs peuvent contenir des caractres gnriques particuliers : Le caractre * correspond nimporte quelle chane de caractres (ventuellement vide). Le caractre ? correspond nimporte quel caractre. Le caractre \ permet de dsactiver linterprtation particulire du caractre suivant. Ainsi, la squence \* correspond lastrisque, \? au point dinterrogation et \\ au caractre backslash (barre oblique inverse). Les crochets [ et ] encadrant une liste de caractres reprsentent nimporte quel caractre contenu dans cette liste. La liste peut contenir un intervalle indiqu par un tiret comme A-Z. Si lon veut inclure les caractres - ou ] dans la liste, il faut les placer en premire ou en dernire position. Les caractres ^ ou ! en tte de liste indiquent que la correspondance se fait avec nimporte quel caractre qui nappartient pas lensemble. Lextraction de motifs peut se faire en dbut, en n, ou au sein dune variable. Il sagit notamment dune technique trs prcieuse pour manipuler les prxes ou les sufxes des noms de chiers. Lexpression ${variable#motif} est remplace par la valeur de la variable, de laquelle on te la chane initiale la plus courte qui corresponde au motif :
$ variable=AZERTYUIOPAZERTYUIOP $ echo ${variable#AZE} RTYUIOPAZERTYUIOP

La portion initiale AZE a t supprime.


$ echo ${variable#*T} YUIOPAZERTYUIOP

Tout a t supprim jusquau premier T.


$ echo ${variable#*[MNOP]} PAZERTYUIOP

valuation dexpressions CHAPITRE 3

35

limination du prxe jusqu la premire lettre contenue dans lintervalle.


$ echo ${variable#*} AZERTYUIOPAZERTYUIOP

Suppression de la plus petite chane quelconque, en loccurrence la chane vide, et il ny a donc pas dlimination de prxe.
Tableau 3-1 : Actions de loprateur ${#...}
variable ${variable#AZE} ${variable#*T} ${variable#*[MNOP]} ${variable#*} AZERTYUIOPAZERTYUIOP RTYUIOPAZERTYUIOP YUIOPAZERTYUIOP PAZERTYUIOP AZERTYUIOPAZERTYUIOP

Lexpression ${variable##motif} sert liminer le plus long prxe correspondant au motif transmis. Ainsi, les exemples prcdents nous donnent :
$ echo ${variable##AZE} RTYUIOPAZERTYUIOP

La chane AZE ne peut correspondre quaux trois premires lettres ; pas de changement par rapport loprateur prcdent.
$ echo ${variable##*T} YUIOP

Cette fois-ci, le motif absorbe le plus long prxe qui se termine par un T.
$ echo ${variable##*[MNOP]}

Le plus long prxe se terminant par M, N, O ou P correspond la chane elle-mme, puisquelle se nit par P. Lexpression renvoie donc une chane vide.
$ echo ${variable##*}

36

Shells Linux et Unix par la pratique

De mme, la suppression de la plus longue chane initiale, compose de caractres quelconques, renvoie une chane vide.
Tableau 3-2 : Actions de loprateur ${##...}
variable ${variable##AZE} ${variable##*T} ${variable##*[MNOP]} ${variable##*} AZERTYUIOPAZERTYUIOP RTYUIOPAZERTYUIOP YUIOP

Symtriquement, les expressions ${variable%motif} et ${variable%%motif} correspondent au contenu de la variable indique, qui est dbarrass, respectivement, du plus court et du plus long sufxe correspondant au motif transmis. En voici quelques exemples :
$ variable=AZERTYUIOPAZERTYUIOP $ echo ${variable%IOP*} AZERTYUIOPAZERTYU $ echo ${variable%%IOP*} AZERTYU $ echo ${variable%[X-Z]*} AZERTYUIOPAZERT $ echo ${variable%%[X-Z]*} A $ echo ${variable%*} AZERTYUIOPAZERTYUIOP $ echo ${variable%%*} $
Tableau 3-3 : Actions des oprateurs ${%...} et ${%%...}
Variable ${variable%IOP*} ${variable%%IOP*} ${variable%[X-Z]*} ${variable%%[X-Z]*} ${variable%*} ${variable%%*} AZERTYUIOPAZERTYUIOP AZERTYUIOPAZERTYU AZERTYU AZERTYUIOPAZERT A AZERTYUIOPAZERTYUIOP

valuation dexpressions CHAPITRE 3

37

Un oprateur est apparu dans Bash 2, qui permet de remplacer une portion de chane grce aux expressions : ${variable/motif/remplacement} qui permet de remplacer la premire occurrence du motif par la chane fournie en troisime position. ${variable//motif/remplacement} qui remplace toutes les occurrences du motif. Cet oprateur remplace la plus longue sous-chane possible correspondant au motif. Il est prsent dans les shells Bash, Korn 93 et Zsh, mais nest pas normalis par Single Unix version 3, et de lgres divergences sont susceptibles dapparatre entre les diffrentes implmentations. Voyons quelques utilisations classiques de ces oprateurs : Afcher le rpertoire de travail en cours, en remplaant le rpertoire personnel par le symbole ~: ${PWD/$HOME/~}
$ cd /etc/X11 $ echo ${PWD/$HOME/~} /etc/X11 $ cd $ echo ${PWD/$HOME/~} ~ $ cd Doc/ScriptLinux/ $ echo ${PWD/$HOME/~} ~/Doc/ScriptLinux $

Retrouver le nom de login dans une adresse e-mail : ${adresse%%@*}


$ adresse=utilisateur@machine.org $ echo ${adresse%%@*} utilisateur $ adresse=utilisateur $ echo ${adresse%%@*} utilisateur $

38

Shells Linux et Unix par la pratique

Rcuprer le nom dhte dans une adresse complte de machine : ${adresse%%.*}


$ adresse=machine.entreprise.com $ echo ${adresse%%.*} machine $ adresse=machine $ echo ${adresse%%.*} machine $

Obtenir le nom dun chier dbarrass de son chemin daccs : ${fichier##*/}


$ fichier=/usr/src/linux/kernel/sys.c $ echo ${fichier##*/} sys.c $ fichier=programme.sh $ echo ${fichier##*/} programme.sh $

liminer lextension ventuelle dun nom de chier : ${fichier%.*}


$ fichier=module1.c $ echo ${fichier%.*} module1 $ fichier=projet.module.h $ echo ${fichier%.*} projet.module $ fichier=module $ echo ${fichier%.*} module $

Renommer tous les chiers, dont lextension est .tgz, qui se trouvent dans le rpertoire courant en chiers .tar.gz:

valuation dexpressions CHAPITRE 3

39

$ ls *.TGZ a.TGZ b.TGZ $ for i in *.TGZ ; do mv $i ${i%TGZ}tar.gz ; done $ ls *.tar.gz a.tar.gz b.tar.gz $

Il est galement possible denchaner les oprateurs ${#}, ${##}, ${%} et ${%%} pour accder divers constituants dune chane de caractres. Par exemple, le script suivant va recevoir sur son entre standard un article Usenet, et extraire la liste des serveurs NNTP par lesquels il est pass. Pour ce faire, il recherche une ligne qui soit constitue ainsi :
Path: serveur_final!precedent!antepenultieme!deuxieme!premier!not-for-mail

Chaque serveur travers par le message ajoute son identit en tte de cette chane. Par convention, la chane Path se termine par un pseudo-serveur not-for-mail. Nous ne nous proccupons pas de ce dtail, et le considrerons comme un serveur normal. Voici le script qui extrait automatiquement la liste des serveurs :
extraction_serveurs.sh: 1 2 3 4 5 6 7 8 9 10 #! /bin/sh ligne_path=$(grep "Path: ") liste_serveurs=${ligne_path##Path: } while [ -n "$liste_serveurs" ] ; do serveur=${liste_serveurs%%!*} liste_serveurs=${liste_serveurs#$serveur} liste_serveurs=${liste_serveurs#!} echo $serveur done

La ligne 3 remplit la variable avec le rsultat de la commande grep, qui recherche la ligne Path: sur son entre standard ; nous verrons plus en dtail cette syntaxe trs prochainement. Cette commande donne le rsultat suivant lorsquon lapplique un message quelconque :
$ grep "Path: " message.news Path: club-internet!grolier!brainstorm.fr!frmug.org!oleane! news-raspail.gip.net!news-stkh.gip.net!news.gsl.net!gip.net !masternews.telia.net!news.algonet.se!newsfeed1.telenordia. se!algonet!newsfeed1.funet.fi!news.helsinki.fi!not-for-mail $

40

Shells Linux et Unix par la pratique

La ligne 4 limine le prxe Path: dans cette ligne. La boucle while, qui stend de la ligne 5 la ligne 10, se droule tant que la variable liste_serveurs nest pas une chane de longueur nulle. Sur la ligne 6, nous extrayons le premier serveur, cest--dire que nous supprimons le plus long sufxe commenant par un point dexclamation. La ligne 7 nous permet de retirer ce serveur de la liste, puis la suivante retire lventuel point dexclamation en tte de liste (qui sera absent lors de la dernire itration). Lexcution de ce script donne le rsultat suivant :
$ ./extraction_serveurs.sh < message.news club-internet grolier brainstorm.fr frmug.org oleane news-raspail.gip.net news-stkh.gip.net news.gsl.net gip.net masternews.telia.net news.algonet.se newsfeed1.telenordia.se algonet newsfeed1.funet.fi news.helsinki.fi not-for-mail $

Les modicateurs #, ##, % et %% de loprateur $ sont, comme nous pouvons le constater, trs puissants ; ils donnent accs de srieuses possibilits de manipulation des chanes de caractres. Cela concerne lextraction des champs contenus dans les lignes dun chier (/etc/passwd par exemple), lautomatisation danalyse de courriers lectroniques ou de groupes de discussion Usenet, ou le dpouillement de journaux de statistiques. Nous verrons que dautres langages, comme Awk par exemple, sont plus adapts pour certaines de ces tches, mais il convient quand mme de ne pas ngliger les possibilits des scripts shell.

valuation dexpressions CHAPITRE 3

41

Longueur de chane

Loprateur $ offre aussi la possibilit de calculer automatiquement la longueur de la chane de caractres reprsentant le contenu dune variable, grce sa forme ${#variable}.
$ variable=azertyuiop $ echo ${#variable} 10 $

La commande grep, utilise prcdemment, renvoyait une longue chane contenant tous les serveurs NNTP :
$ variable=$(grep Path message.news ) $ echo ${#variable} 236 $

Si la variable est numrique, sa longueur correspond celle de la chane de caractres qui la reprsente. Cela est galement vrai pour les variables qui sont explicitement dclares arithmtiques. Par exemple, la variable EUID est une variable arithmtique dnie automatiquement par le shell.
$ echo $EUID 500 $ echo ${#EUID} 3 $

Les variables vides ou non dnies ont des longueurs nulles.


$ variable= $ echo ${#variable} 0 $ echo ${#inexistante} 0 $

42

Shells Linux et Unix par la pratique

Actions par dfaut

Lorsquun script fonde son comportement sur des variables qui sont fournies par lutilisateur, il est important de pouvoir congurer une action par dfaut, dans le cas o la variable naurait pas t remplie. Loprateur ${} vient encore notre aide avec lappui de quatre modicateurs. Lexpression ${variable:-valeur} prend la valeur indique droite du modicateur :- si la variable nexiste pas, ou si elle est vide. Cela nous permet de fournir une valeur par dfaut. Naturellement, si la variable existe et est non vide, lexpression renvoie son contenu :
$ variable=existante $ echo ${variable:-defaut} existante $ variable= $ echo ${variable:-defaut} defaut $ echo ${inexistante:-defaut} defaut $

Dans le script rm_secure du chapitre 2, nous avons x systmatiquement en dbut de programme la variable sauvegarde_rm=~/.rm_saved/. Si le script se trouve dans un rpertoire systme (/usr/local/bin), lutilisateur ne peut pas le modier. Il peut toutefois prfrer employer un autre rpertoire pour la sauvegarde. Cela peut tre ralis en lautorisant xer le contenu dune variable denvironnement disons SAUVEGARDE_RM avant dinvoquer rm_secure. On utilisera alors le contenu de cette variable, sauf si elle est vide, auquel cas on emploiera la chane ~/.rm_saved/ par dfaut. Ainsi la premire ligne du script deviendrait-elle :
1 sauvegarde_rm=${SAUVEGARDE_RM:-~/.rm_saved/}

Lemploi systmatique de valeurs par dfaut en dbut de script permet damliorer la robustesse du programme, et de le rendre plus souple, plus facile congurer par lutilisateur. Le contenu mme de la variable nest pas modi. Toutefois, il est des cas o cela serait prfrable. La construction ${variable:=valeur} peut tre utilise cet effet. Si la variable nexiste 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.
$ echo $vide $ echo ${vide:=contenu} contenu $ echo $vide contenu $

valuation dexpressions CHAPITRE 3

43

Lorsquil sagit simplement de remplir la variable avec une valeur par dfaut, le retour de lexpression ne nous intresse pas. On ne peut toutefois pas lignorer purement et simplement, car le shell le considrerait comme une commande et dclencherait une erreur :
$ ${vide:=contenu} bash: contenu: command not found $

Lutilisation de la commande echo, mme redirige vers /dev/null, manque franchement dlgance. Il est toutefois possible de demander au shell dignorer le rsultat de lvaluation de lexpression grce la commande interne deux-points : qui signie aucune opration . En reprenant lexemple rm_secure, on peut dcider que lutilisateur peut xer directement le contenu de la variable sauvegarde_rm sil le souhaite avant dappeler le script. La premire ligne deviendra donc :
1 : ${sauvegarde_rm:=~/.rm_saved/}

Dans certaines situations, le script peut vraiment avoir besoin dune valeur quil ne peut pas deviner. Si la variable en question nexiste pas, ou si elle est vide, le seul comportement possible est dabandonner le travail en cours. La construction ${variable:?message} ralise cette opration dune manire simple et concise. Si la variable est dnie et non vide, sa valeur est renvoye. Sinon, le shell afche le message fourni aprs le point dinterrogation, et abandonne le script ou la fonction en cours. Le message est prx du nom de la variable. Comme prcdemment, pour simplement vrier si la variable est dnie ou non, sans utiliser la valeur renvoye, on peut utiliser la commande :. Nous allons insrer le test dans une petite fonction (dnie directement sur la ligne de commande) pour vrier que son excution sarrte bien quand le test choue.
$ function ma_fonction > { > : ${ma_variable:?"n est pas dfinie"} > echo ma_variable = $ma_variable > } $ ma_fonction bash: ma_variable: n est pas dfinie $ ma_variable=son_contenu $ ma_fonction ma_variable = son_contenu $

Si le message nest pas prcis, le shell en afche un par dfaut :


$ echo ${inexistante:?} bash: inexistante: parameter null or not set $

44

Shells Linux et Unix par la pratique

On dispose dune dernire possibilit pour vrier si une variable est dnie ou non. Utilise plus rarement, la construction ${variable:+valeur} renvoie la valeur fournie droite du symbole :+ si la variable est dnie et non vide, sinon elle renvoie une chane vide.
$ existante=4 $ echo ${existante:+1} 1 $ echo ${inexistante:+1} $

Cette opration est surtout utile en la couplant avec le modicateur := pour obtenir une valeur prcise si une variable est dnie, et une autre valeur si elle ne lest pas. Ici, la variable definie prendra pour valeur oui si var_testee est dnie et non vide, et non dans le cas contraire :
$ var_testee= $ definie=${var_testee:+oui} $ : ${definie:=non} $ echo $definie non $ var_testee=1 $ definie=${var_testee:+oui} $ : ${definie:=non} $ echo $definie oui $

Les quatre modicateurs prcdents considrent au mme titre les variables indnies et les variables contenant une chane vide. Il existe quatre modicateurs similaires qui nagissent que si la variable est vraiment indnie ; il sagit de ${-}, ${=}, ${?}, et ${+}:
$ unset indefinie $ vide="" $ echo ${indefinie-dfaut} dfaut $ echo ${vide-dfaut} $

Calcul arithmtique
Le shell permet de raliser des calculs arithmtiques simples (on se limitera aux valeurs entires pour plus de portabilit), ce qui est parfois trs prcieux. Nous verrons plus avant que lutilitaire systme bc nous permettra de raliser des oprations en virgule ottante.

valuation dexpressions CHAPITRE 3

45

Les calculs arithmtiques sont reprsents par loprateur $((oprations)). Il est dconseill dutiliser sa forme ancienne, $[oprations], car elle est considre comme obsolte. Les oprateurs arithmtiques disponibles sont les mmes que dans la plupart des langages de programmation : +, -, *, et / pour les quatre oprations de base ; % pour le modulo ; << et >> pour les dcalages binaires gauche et droite ; &, |, et ^ pour les oprateurs binaires ; Et, Ou et Ou Exclusif ; et nalement ~ pour la ngation binaire. Les oprateurs peuvent tre regroups entre parenthses pour des questions de priorit. On peut placer des blancs (espaces) volont pour amliorer la lisibilit du code.
$ echo $((2 * (4 + (10/2)) - 1)) 17 $ echo $((7 % 3)) 1

Une constante numrique est considre par dfaut en base 10. Si elle commence par 0, elle est considre comme tant octale (base 8), et par 0x comme hexadcimale (base 16). On peut aussi employer une reprsentation particulire base#nombre o lon prcise explicitement la base employe (jusqu 36 au maximum). Cela peut servir surtout pour manipuler des donnes binaires :
$ $ $ 2 $ 14 $ 12 $ 24 $ masque=2#000110 capteur=2#001010 echo $(($masque & $capteur)) echo $(($masque | $capteur)) echo $(($masque ^ $capteur)) echo $(($masque << 2))

Il arrive que les donnes numriques dun calcul provienne dun programme externe ou dune saisie de lutilisateur, et rien ne garantit quelles ne commenceront pas par un zro, mme si elles sont en dcimal. Supposons que lutilisateur remplisse la variable x=010, le calcul $(($x+1)) renverrait le rsultat (dcimal) 9, car le shell interprterait le contenu de x comme une valeur octale. Pour viter ceci, on peut forcer linterprtation en dcimal avec $((10#$x+1)). Ceci est utile lorsquon rcupre des nombres provenant de chiers formats avec des colonnes de chiffres complts gauche par des zros.

46

Shells Linux et Unix par la pratique

Les variables qui se trouvent lintrieur de la structure de calcul $(()) ne sont pas tenues dtre prcdes par le caractre $, mais cela amliore gnralement la lisibilit du programme. Si la variable contient une chane de caractres reprsentant une opration, une diffrence entre les notations avec et sans $ peut apparatre par le jeu des priorits dvaluation :
$ a=1+2 $ echo $((a)) 3 $ echo $(($a)) 3

Jusque-l aucune diffrence. Enchanons avec une multiplication :


$ echo $(($a*2)) 5 $ echo $((a*2)) 6 $

La variable prcde du $ est remplace par sa valeur littrale avant de faire lvaluation globale. La variable sans $ est value, puis son rsultat est plac dans lexpression avant le calcul nal. Si une variable nest pas dnie, est vide, ou contient une chane non numrique, elle est interprte comme une valeur nulle. Il est aussi possible de raliser, au sein de la structure $(()), une ou plusieurs affectations de variables laide de loprateur =.
$ echo $((x = 5 - 2)) 3 $ echo $x 3 $ echo $((y = x * x + x + 1)) 13 $ echo $y 13 $

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)).

valuation dexpressions CHAPITRE 3

47

Une affectation renvoie la valeur calcule, ce qui permet de la rutiliser dans une autre opration : $((a*=b+=2)) correspond $((b=b+2)) suivie de $((a=a*b)). Bien entendu, ce genre dcriture, franchement illisible, est totalement dconseill. La structure $(()) peut galement servir vrier des conditions arithmtiques. Les oprateurs de comparaison renvoient la valeur 1 pour indiquer quune condition est vrie, et 0 sinon. Il sagit des comparaisons classiques <, <=, >=, >, ainsi que == pour lgalit et != pour la diffrence. Les conditions peuvent tre associes par un Et Logique && ou un Ou Logique ||, ou encore tre nies avec !.
$ echo $(((25 + 2) < 28)) 1 $ echo $(((12 + 4) == 17)) 0 $ echo $(((1 == 1) && (2 < 3))) 1 $

Nous verrons plus avant comment employer ces conditions dans des constructions ifthen, while-do, etc.

Invocation de commande
Une dernire constructionn base de loprateur $ est appele substitution de commande . Elle a la forme suivante : $(commande). La forme `commande`, probablement hrite du shell C, est quant elle peu recommande, car moins lisible et difcile imbriquer. La commande qui se trouve entre les parenthses est excute, et tout ce quelle a crit sur sa sortie standard est captur et plac son emplacement sur la ligne de commande. Par exemple linvocation :
$ variable=$(ls -l)

placera le rsultat de la commande ls dans la variable. Les caractres de saut de ligne qui se trouvent la n de la chane sont limins, mais ceux qui sont rencontrs dans le cours de la chane sont conservs. Lorsquon dsire examiner le contenu dune telle variable, il ne suft donc pas dappeler :
$ echo $variable

En effet, tout le contenu de la chane de caractres est ainsi plac sur la ligne de commande de echo, y compris les ventuelles successions despaces multiples, les tabulations et les retours la ligne. Lors de linterprtation de cette ligne de commande, le

48

Shells Linux et Unix par la pratique

shell va remplacer tous ces lments, quil considre comme des sparateurs darguments, par des espaces uniques. En voici un exemple :
$ cd /var/spool/lpd/ $ ls -l total 3 drwxr-xr-x -rw-r--r-drwxr-xr-x 2 root 1 root 2 root lp root lp 1024 Sep 21 13:59 lp0 4 Oct 3 23:47 lpd.lock 1024 Oct 2 18:35 photo

$ variable=$(ls -l) $ echo $variable total 3 drwxr-xr-x 2 root lp 1024 Sep 21 13:59 lp0 -rw-r--r-- 1 root root 4 Oct 3 23:47 lpd.lock drwxr-xr-x 2 root lp 1024 Oct 2 18:35 photo $

Pour afcher correctement le contenu dune variable qui renferme des espaces multiples, des tabulations et des retours la ligne, il faut que ce contenu soit protg de linterprtation du shell, ce qui sobtient en lencadrant par des guillemets. Nous reviendrons sur ce mcanisme.
$ echo "$variable" total 3 drwxr-xr-x -rw-r--r-drwxr-xr-x $ 2 root 1 root 2 root lp root lp 1024 Sep 21 13:59 lp0 4 Oct 3 23:47 lpd.lock 1024 Oct 2 18:35 photo

Portes et attributs des variables


Les variables employes par le shell sont caractrises par leur porte, cest--dire par une certaine visibilit dans les sous-processus, fonctions, etc. Une variable qui est simplement dnie avec une commande variable=valeur, sans autres prcisions, est accessible dans lensemble du processus en cours. Cela comprend les fonctions que lon peut invoquer :
$ $ > > > var=123 function affiche_var { echo $var }

valuation dexpressions CHAPITRE 3

49

$ affiche_var 123 $ var=456 $ affiche_var 456 $

La fonction a accs au contenu de la variable en lecture, mais aussi en criture :


$ function modifie_var > { > > } $ var=456 $ echo $var 456 $ modifie_var $ echo $var 123 $ var=123

Une fonction peut aussi dnir une variable qui nexistait pas jusqualors, et qui sera dsormais accessible par le reste du processus, mme aprs la n de la fonction. Pour observer ce phnomne, il faut congurer le shell avec la commande set-u pour quil fournisse un message derreur si on tente daccder une variable indnie.
$ set -u $ function definit_var > { > > } $ echo $nouvelle_var bash: nouvelle_var: unbound variable $ definit_var $ echo $nouvelle_var 123 $ nouvelle_var=123

50

Shells Linux et Unix par la pratique

Une variable qui est dnie sans autres prcisions est partage avec les scripts que lon excute avec la commande sourcefichier (ou le point .fichier). Nous avons utilis cette proprit dans le script rm_secure du chapitre 2, et la variable sauvegarde_rm. En revanche, la variable nest accessible ni par un script invoqu par lintermdiaire dun sous-processus, ni par le processus pre qui a lanc le shell en cours.
Restriction de porte

La seule restriction possible de visibilit concerne les fonctions. Il est possible dy dclarer des variables locales qui ne seront pas accessibles dans le processus suprieur. Elles seront en revanche parfaitement disponibles pour les sous-fonctions ventuellement invoques. Lorsquon dclare une variable avec le mot-cl local, et quelle existait dj au niveau suprieur du script, la nouvelle instance masque la prcdente jusquau retour de la fonction. Le script suivant va nous permettre de mettre en vidence ce comportement.
var_locales.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #! /bin/sh function ma_fonction () { local var="dans fonction" echo " entre dans ma_fonction" echo " var = " $var echo " appel de sous_fonction" sous_fonction echo " var = " $var echo " sortie de ma_fonction" } function sous_fonction () { echo " entre dans sous_fonction" echo " var = " $var echo " modification de var" var="dans sous_fonction" echo " var = " $var echo " sortie de sous_fonction" } echo "entre dans le script" var="dans le script" echo "var = " $var echo "appel de ma_fonction" ma_fonction echo "var = " $var

valuation dexpressions CHAPITRE 3

51

Linvocation rsume bien laccs uniquement local aux variables :


$ ./var_locales.sh entre dans le script var = dans le script appel de ma_fonction entre dans ma_fonction var = dans fonction appel de sous_fonction entre dans sous_fonction var = dans fonction modification de var var = dans sous_fonction sortie de sous_fonction var = dans sous_fonction sortie de ma_fonction var = dans le script $

On notera, toutefois, que le comportement des variables locales nest pas tout fait le mme que dans dautres langages de programmation. On peut constater que, dans les scripts shell, le masquage dune variable globale par une variable locale concerne aussi les sous-fonctions qui peuvent tre appeles partir de la routine en question. En C ou C++ par exemple, si une variable locale dune routine a le mme nom quune variable globale, la superposition ne recouvre que la porte de la routine en question, et pas les fonctions quelle peut appeler. En Perl et ses drivs, les deux comportements sont possibles suivant lemploi du mot-cl local ou de my.
Extension de porte Environnement

Nous avons remarqu que, lorsquun processus lance un script par lintermdiaire dun appel ./script, il ne lui transmet pas les variables dnies dune manire courante. Ces variables, en effet, sont places en mmoire dans une zone de travail du shell, qui nest pas copie lorsquun nouveau processus dmarre pour excuter le script. Il est parfois indispensable de transmettre des donnes ce nouveau script et il faut donc trouver un moyen de placer les variables dans une zone de mmoire qui sera copie dans lespace du nouveau processus. Cette zone existe : cest lenvironnement du processus. Lenvironnement fait partie des lments qui sont hrits lorsquun processus cre grce lappel systme fork() un nouveau processus ls. Pour quune variable y soit disponible, il faut quelle soit marque comme exportable. On obtient cela tout simplement

52

Shells Linux et Unix par la pratique

grce la commande interne export. Pour observer le comportement de ce mcanisme, nous dnissons des variables, en exportons une partie, invoquons un nouveau shell (donc un sous-processus) et examinons celles qui sont visibles :
$ $ $ $ $ $ var1="variable non-exporte" var2="premire variable exporte" export var2 export var3="seconde variable exporte" /bin/sh echo $var1

$ echo $var2 premire variable exporte $ echo $var3 seconde variable exporte $

Une variable qui se trouve dans lenvironnement, lors du dmarrage dun processus, est automatiquement exporte pour ses futurs processus ls. On peut le vrier en continuant la manipulation et en invoquant un nouveau shell.
$ var2="variable modifie dans le second shell" $ /bin/sh $ echo $var2 variable modifie dans le second shell $ echo $var3 seconde variable exporte $ exit exit $

La variable var2 a t modie dans lenvironnement du sous-shell (et de son ls), mais elle ne le sera pas dans lenvironnement du shell original. Vrions cela en remontant dun niveau :
$ exit exit $ echo $var2 premire variable exporte $

valuation dexpressions CHAPITRE 3

53

La commande export marque les variables transmises en argument comme exportables (par exemple, exportvar1var2), mais peut aussi regrouper une ou plusieurs affectations de variables sur la mme ligne, par exemple :
$ export v1=1 v2=2 $ echo $v1 $v2 1 2 $

La fonction export offre plusieurs options avec lesquelles il est possible de manipuler lenvironnement. export -p afche la liste des lments marqus comme exportables :
$ export -p declare -x BASH_ENV="/home/ccb/.bashrc" declare -x HISTFILESIZE="1000" declare -x HISTSIZE="1000" declare -x HOME="/home/ccb" declare -x HOSTNAME="venux.ccb" declare -x HOSTTYPE="i386" declare -x INPUTRC="/etc/inputrc" declare -x KDEDIR="/usr" declare -x LANG="fr_FR" declare -x LC_ALL="fr_FR" declare -x LINGUAS="fr" declare -x LOGNAME="ccb" declare -x MAIL="/var/spool/mail/ccb" declare -x OSTYPE="Linux" declare -x PATH="/bin:/usr/bin:/usr/X11R6/bin:/usr/local/bin" declare -x PS1="\$ " declare -x QTDIR="/usr/lib/qt-2.0.1" declare -x SHELL="/bin/bash" declare -x SHLVL="1" declare -x TERM="ansi" declare -x USER="ccb" declare -x USERNAME="" $

54

Shells Linux et Unix par la pratique

On remarque que les variables sont prcdes avec Bash dun declare-x qui est synonyme de export pour ce shell. Avec Ksh les variables sont prcdes du mot-cl export. On peut noter aussi que par convention le nom des variables denvironnement est crit en majuscules. Ce nest toutefois quune simple convention. Les deux options suivantes de export sont spciques Bash. Loption export-n enlve lattribut exportable dune variable. Elle nest en revanche pas supprime pour autant ; elle sera seulement indisponible dans les processus ls, comme en tmoigne lexemple suivant :
$ export var1="exporte" $ /bin/sh $ echo $var1 exporte $ exit exit $ export -n var1 $ echo $var1 exporte $ /bin/sh $ echo $var1 $ exit exit $

export-f permet dexporter une fonction. Par dfaut, les fonctions ne sont pas copies dans lenvironnement du processus ls. La commande interne declare-f afche la liste de celles qui sont dnies dans la zone de travail en cours (mais pas ncessairement exportes). Nous voyons par exemple que, sur notre machine, la fonction rm dnie dans le chapitre prcdent nest pas exporte dans lenvironnement :
$ declare -f declare -f rm () { local opt_force=0; local opt_interactive=0; ... mv -f "$1" "${sauvegarde_rm}/"; shift; done }

valuation dexpressions CHAPITRE 3

55

$ export -f $ sh $ declare -f $ exit exit $

Pour quelle soit disponible dans un sous-shell, nous devons ajouter, dans le script rm_secure, la ligne suivante aprs la dnition de la fonction :
106 export -f rm

De mme, nous devons ajouter la commande export sur la ligne numro 1 an que la variable sauvegarde_rm soit galement disponible dans les sous-shells. Ainsi, lors de la connexion suivante, la fonction est bien disponible dans lenvironnement des sous-shells :
$ export -f declare -f rm () { local opt_force=0; ... done } $ sh $ export -f declare -f rm () { local opt_force=0; ... done } $ exit exit $

Les options de la commande export sont cumulables (par exemple, -f n, pour annuler lexportation dune fonction). Le tableau suivant en rappelle la liste, ainsi que les options quivalentes de la commande declare de Bash (qui en compte dautres que nous verrons

56

Shells Linux et Unix par la pratique

ci-aprs). Pour cette dernire, une option prcde dun + a leffet inverse de loption prcde dun .
Option export Option declare Signication
Marque la variable pour quelle soit exporte dans lenvironnement. Afche la liste des lments exports. Retire lattribut exportable de la variable. Exporte une fonction et non pas une variable.

export var export -p export -n var export -f fonct

declare -x var declare declare +x var declare -f fonct

Le contenu actuel de lenvironnement peut aussi tre consult avec un utilitaire systme nomm printenv sous Linux. Il est important de bien comprendre quune variable exporte est simplement copie depuis lenvironnement de travail du shell pre vers celui du shell ls. Elle nest absolument pas partage entre les deux processus, et aucune modication apporte dans le shell ls ne sera visible dans lenvironnement du pre.
Attributs des variables

Nous avons dj vu quune variable pouvait tre dote dun attribut exportable qui permet de la placer dans une zone mmoire qui sera copie dans lenvironnement de travail du processus. Deux autres attributs sont galement disponibles, que lon peut activer laide de la commande declare de Bash ou typeset de Ksh.
Variable arithmtique

On peut indiquer avec declare-i ou typeset-i que le contenu dune variable sera strictement arithmtique. Lors de laffectation dune valeur cette variable, le shell procdera systmatiquement une phase dvaluation arithmtique exactement semblable ce quon obtient avec loprateur $(()). En fait, on peut considrer que, si une variable A est dclare arithmtique, toute affectation du type A=xxx sera value sous la forme A=$((xxx)). Si la valeur que lon essaie dinscrire dans une variable arithmtique est une chane de caractres ou est indnie, la variable est remplie avec un zro. Voici quelques exemples pour se xer les ides : A est arithmtique, B et C ne le sont pas. On constate tout dabord quune chane de caractres, 1+1 en loccurrence, est value diffremment suivant le contexte.
$ declare -i A $ A=1+1 $ echo $A 2 $ B=1+1 $ echo $B 1+1 $

valuation dexpressions CHAPITRE 3

57

Ce rsultat a t obtenu avec Bash, mais le mme comportement est observ avec Ksh en remplaant declare-iA par typeset-iA. Si on veut insrer des espaces dans la formule, il faut la regrouper en un seul bloc au moyen de guillemets ; nous reviendrons sur ce point ultrieurement.
$ A=4*7 + 67 sh: +: command not found $ A="4*7 + 67" $ echo $A 95

Lors de laffectation dune variable arithmtique, le contenu des variables ventuellement contenues dans lexpression est valu sous forme arithmtique :
$ C=5*7 $ echo $C 5*7 $ A=C $ echo $A 35 $

Nous pouvons dailleurs remarquer qu linstar de la construction $(()), il ntait pas indispensable de mettre un $ devant le C pour en prendre la valeur (cela amliorerait quand mme la lisibilit). Vrions nalement quune valeur non arithmtique est bien considre comme un zro :
$ A="ABC" $ echo $A 0 $

Les commandes declare-i ou typeset -i seules afchent la liste des variables arithmtiques. La commande declare+ivar avec Bash, ou typeset+ivar avec Ksh, supprime lattribut arithmtique dune variable. Les variables arithmtiques amliorent la lisibilit dun script qui ralise de multiples calculs et en facilitent lcriture.

58

Shells Linux et Unix par la pratique

Variable en lecture seule

Loption -r des commandes declare ou typeset permet de ger une variable an quelle ne soit plus accessible quen lecture (ce qui en fait nalement une constante et non plus une variable !). Cela ne concerne toutefois que le processus en cours ; si la variable est exporte vers un shell ls, ce dernier pourra la modier dans son propre environnement. Voyons quelques exemples :
$ A=Immuable $ echo $A Immuable $ declare -r A $ A=Modifie bash: A: read-only variable $ echo $A Immuable $ unset A unset: A: cannot unset: readonly variable $ export A $ sh $ echo $A Immuable $ A=Change $ echo $A Change $ exit exit $ echo $A Immuable $

Bash dnit automatiquement quelques constantes non modiables, concernant lidentication du processus en cours.
$ declare -r declare -ri EUID="500" declare -ri PPID="13881" declare -ri UID="500" $

valuation dexpressions CHAPITRE 3

59

On y recourt principalement pour dnir des constantes en dbut de script, par exemple des limites ou des reprsentations symboliques de valeurs numriques. On peut alors les complter avec loption i, qui indique quil sagit de valeurs arithmtiques, par exemple :
declare declare declare declare -r -ri -ri -ri MSG_ACCUEIL="Entrez votre commande :" NB_PROCESSUS_MAX=256 BIT_LECTURE=0x0200 BIT_ECRITURE=0x0100

Paramtres
Dans la terminologie de la programmation shell, le concept de variable est inclus dans une notion plus large, celle de paramtres. Il sagit pour lessentiel de donnes en lecture seule, que le shell met disposition pour que lon puisse obtenir des informations sur lexcution du script. Il en existe deux catgories : les paramtres positionnels et les paramtres spciaux.

Paramtres positionnels
Les paramtres positionnels sont utiliss pour accder aux informations qui sont fournies sur la ligne de commande lors de linvocation dun script, mais aussi aux arguments transmis une fonction. Les arguments sont placs dans des paramtres qui peuvent tre consults avec la syntaxe $1, $2, $3, etc. Pour les paramtres positionnels, comme pour les paramtres spciaux qui seront vus ci-aprs, laffectation est ipso facto impossible, car on ne peut pas crire 1=2! Le premier argument transmis est accessible en lisant $1, le deuxime dans $2, et ainsi de suite. Pour consulter le contenu dun paramtre qui comporte plus dun chiffre, il faut lencadrer par des accolades ; ainsi, ${10} correspond bien au contenu du dixime argument, tandis que $10 est une chane constitue de la valeur du premier argument, suivie dun zro. Par convention, largument numro zro contient toujours le nom du script tel quil a t invoqu. Le script suivant afche les arguments existants, jusquau dixime, en vriant chaque fois si la chane correspondante nest pas vide :
affiche_arguments.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 #! /bin/sh echo if [ if [ if [ if [ if [ if [ if [ if [ if [ if [ 0 : $0 -n "$1" ] -n "$2" ] -n "$3" ] -n "$4" ] -n "$5" ] -n "$6" ] -n "$7" ] -n "$8" ] -n "$9" ] -n "${10}"

; ; ; ; ; ; ; ; ; ]

then echo 1 then echo 2 then echo 3 then echo 4 then echo 5 then echo 6 then echo 7 then echo 8 then echo 9 ; then echo

: : : : : : : : : 10

$1 ; fi $2 ; fi $3 ; fi $4 ; fi $5 ; fi $6 ; fi $7 ; fi $8 ; fi $9 ; fi : ${10} ; fi

60

Shells Linux et Unix par la pratique

Ce script nest ni lgant ni trs efcace, mais nous en verrons une version amliore plus bas. Il permet quand mme de rcuprer le contenu des arguments :
$ ./affiche_arguments.sh 0 : ./affiche_arguments.sh $ ./affiche_arguments.sh premier deuxime troisime 0 : ./affiche_arguments.sh 1 : premier 2 : deuxime 3 : troisime $ ./affiche_arguments.sh a b c d e f g h et dix 0 : ./affiche_arguments.sh 1 : a 2 : b 3 : c 4 : d 5 : e 6 : f 7 : g 8 : h 9 : et 10 : dix $

Lors de linvocation dune fonction, les paramtres positionnels sont temporairement remplacs par les arguments fournis lors de lappel. Le script suivant afche le contenu de $0, $1, $2 dans et en dehors dune fonction :
affiche_arguments_2.sh: 1 2 3 4 5 6 7 8 9 10 #! /bin/sh function { echo echo echo echo } fonct () " " " " Dans la fonction : " 0 : $0" 1 : $1" 2 : $2"

valuation dexpressions CHAPITRE 3

61

11 12 13 14 15 16

echo "Dans le script : " echo 0 : $0 echo 1 : $1 echo 2 : $2 echo "Appel de : fonct un deux" fonct un deux

Nous pouvons remarquer quil ny a pas de diffrence, sur le paramtre $0, entre lintrieur et lextrieur de la fonction :
$ ./affiche_arguments_2.sh premier deuxime Dans le script : 0 : ./affiche_arguments_2.sh 1 : premier 2 : deuxime Appel de : fonct un deux Dans la fonction : 0 : ./affiche_arguments_2.sh 1 : un 2 : deux $

Nous avons indiqu plus haut quil ntait pas possible daffecter une valeur un paramtre positionnel par une simple phrase 1=xxx; malgr tout, il est quand mme possible de modier son contenu. Toutefois, la modication porte sur lensemble des arguments. La fonction set permet, entre autres choses, de xer la valeur des paramtres $1, $2, etc. Les valeurs transmises sur la ligne de commande de set, et qui ne font pas partie des (nombreuses) options de cette dernire, sont utilises pour remplir les paramtres positionnels.
$ set a b c $ echo $1 $2 $3 a b c $ set d e $ echo $1 $2 $3 d e $

On remarquera que cest lensemble des paramtres positionnels qui est modi ; la valeur de $3 dans notre exemple est supprime, mme si on ne fournit que deux arguments set.

62

Shells Linux et Unix par la pratique

On peut aussi modier les paramtres positionnels dans un script en invoquant la commande shift. Celle-ci va dcaler lensemble des paramtres : $1 est remplac par $2, celui-ci par $3, et ainsi de suite. Le paramtre $0 nest pas concern par cette commande.
Paramtre Avant shift Aprs shift

$0 $1 $2 $3

./mon_script azerty uiop qsdf ghi

./mon_script uiop qsdf ghi (vide)

$ set a b c d e $ echo $0 $1 $2 $3 -bash a b c $ shift $ echo $0 $1 $2 $3 -bash b c d $ shift $ echo $0 $1 $2 $3 -bash c d e $ shift $ echo $0 $1 $2 $3 -bash c d $

Si linstruction shift est suivie dun nombre, ce dernier reprsente le nombre de dcalages dsirs. Par dfaut, il sagit dun seul dcalage mais, par exemple, shift3 permet de remplacer $1 par $3, $2 par $4, etc. Il est possible de rcrire une version un peu plus lgante du programme dafchage des arguments, dans laquelle on boucle sur echo et shift tant que $1 nest pas vide.
affiche_arguments_3.sh: 1 2 3 4 5 6 #! /bin/sh while [ -n "$1" ] ; do echo $1 shift done

valuation dexpressions CHAPITRE 3

63

Le rsultat correspond nos attentes :


$ ./affiche_arguments_3.sh un deux trois quatre un deux trois quatre $

Dans un vrai script, lanalyse des arguments de la ligne de commande doit tre mene avec soin. Nous avons dj rencontr une commande nomme getopts qui permet de rechercher les options dans la liste des arguments ; nous reviendrons sur cette fonction dans le chapitre 5.

Paramtres spciaux
Nos programmes dafchage des arguments ne sont pas sufsants, car ils ne sont pas capables de faire la diffrence entre un argument qui reprsente une chane vide (saisi avec "" sur la ligne de commande) et la n de la liste des arguments. En voici la preuve :
$ ./affiche_arguments.sh un "" deux trois 0 : ./affiche_arguments.sh 1 : un 3 : deux 4 : trois $ ./affiche_arguments_3.sh un deux "" trois quatre un deux $

Le premier script na pas afch $2 car il la vu vide ; quant au second il sest tout simplement arrt aprs avoir rencontr un $3 vide, pensant tre arriv la n des arguments. Pour corriger ce problme, il nous faudrait connatre le nombre de paramtres positionnels. Par chance, le shell nous y donne accs travers lun de ses paramtres spciaux. Il sagit simplement de paramtres en lecture seule qui nous donnent des indications sur lenvironnement dexcution du processus. Comme pour les paramtres positionnels, le nom des divers paramtres spciaux ($$, $*, $#, etc.) fait quil nest pas possible de leur affecter une valeur avec la construction =. Ils ne sont donc, de fait, accessibles quen lecture seule.

64

Shells Linux et Unix par la pratique

Le nombre darguments, sans compter le paramtre $0, est contenu dans le paramtre $#. Cette valeur est naturellement modie par shift. Nous pouvons amliorer le script prcdent de faon quil attende que $# soit gal zro avant de sarrter.
affiche_arguments_4.sh: 1 2 3 4 5 6 #! /bin/sh while [ $# -ne 0 ]; do echo $1 shift done

Cette fois-ci, les arguments vides sont traits correctement :


$ ./affiche_arguments_4.sh un deux "" trois "" quatre cinq un deux trois quatre cinq $

Il est souvent utile de pouvoir manipuler en une seule fois lensemble des paramtres positionnels, an de les transmettre une fonction, un autre script, une commande, etc. Pour ce faire, on dispose de deux paramtres spciaux, $* et $@. Le premier est quasiment obsolte, et lon utilise toujours la squence "$@". Voyons pourquoi. Lun comme lautre vont se dvelopper pour reprsenter lensemble des paramtres positionnels. Avec $*, tous les paramtres vont tre crits les uns la suite des autres. Le script suivant va invoquer affiche_arguments_5.sh, en lui transmettant lensemble de ses propres arguments :
affiche_arguments_5.sh: 1 2 3 #! /bin/sh .:affiche_arguments_5.sh $*

Voici un exemple dexcution :


$ 0 1 2 3 $ ./affiche_arguments_5.sh un deux trois :./affiche_arguments_5.sh :un :deux :trois

valuation dexpressions CHAPITRE 3

65

Apparemment, tout fonctionne bien. Pourtant, un problme se pose lorsquun des arguments est une chane de caractres qui contient dj un espace :
$ ./affiche_arguments_5.sh un deux "trois et quatre" 0 :./affiche_arguments_5.sh 1 :un 2 :deux 3 :trois 4 :et 5 :quatre $

Toutefois, si nous appelons directement affiche_arguments_5.sh, le rsultat est diffrent :


$ ./affiche_arguments_5.sh un deux "trois et quatre" 0 :./affiche_arguments_5.sh 1 :un 2 :deux 3 :trois et quatre $

En fait, lors de lvaluation de $*, tous les guillemets sont supprims, et les arguments sont mis bout bout. Si lun deux contient un espace, il est scind en plusieurs arguments. Cela peut paratre anodin, mais cest en ralit trs gnant. Supposons par exemple que nous souhaitions crer, comme cest souvent lusage, un script nomm ll qui serve invoquer ls avec loption -l, pour avoir un afchage long du contenu dun rpertoire. Notre premire ide pourrait tre celle-ci :
ll_1.sh: 1 2 #! /bin/sh ls -l $*

premire vue, ce script fonctionne correctement :


$ ./ll_1.sh c* -rwxr-xr-x 1 cpb -rwxr-xr-x 1 cpb cpb cpb 560 Oct 4 13:23 script_1.sh 551 Oct 17 15:54 script_2.sh 3 Aug 12 1999 /dev/cdrom -> hdc

$ ./ll_1.sh /dev/cdrom lrwxrwxrwx 1 root root $

66

Shells Linux et Unix par la pratique

Pourtant, si nous rencontrons un chier dont le nom contient un blanc typographique (comme cest souvent le cas sur les partitions Windows), le rsultat est diffrent :
$ ls -d /mnt/dos/P* /mnt/dos/Program Files $ ./ll_1.sh -d /mnt/dos/P* ls: /mnt/dos/Program: Aucun fichier ou rpertoire de ce type ls: Files: Aucun fichier ou rpertoire de ce type $

Loption -d que nous avons ajoute demande ls dafcher le nom du rpertoire, Program Files en loccurrence, et non pas son contenu. Le problme est que notre script a invoqu ls en lui passant sur la ligne de commande le nom du chier spar en deux morceaux. En fait, ls a reu en argument $1 loption -d, en $2 le mot /mnt/dos/Program, et en $3 le mot Files. On peut alors sinterroger sur le comportement de $* lorsquil est encadr par des guillemets. Le rsultat du dveloppement sera bien conserv en un seul morceau, sans sparer les deux moitis du nom de chier. Hlas, le problme nest toujours pas rgl, car lorsque "$*" est valu, il est remplac par la liste de tous les arguments regroups au sein dune mme expression, encadre par des guillemets. Si nous essayons de lutiliser pour notre script, le fonctionnement en sera pire encore :
ll_2.sh: 1 2 #! /bin/sh ls -l "$*"

Ce script pourrait en effet fonctionner avec un nom de chier contenant un blanc. Toutefois, tous les arguments tant regroups en un seul, il ne pourrait accepter quun seul nom de chier la fois :
$ ./ll_2.sh /mnt/dos/Mes documents/Etiquettes CD.cdr -rw-rw-r-- 1 cpb CD.cdr $ ./ll_2.sh c* ls: script_1.sh script_2.sh: Aucun fichier ou rpertoire de ce type $ cpb 12610 Nov 27 2006 /mnt/dos/Mes documents/Etiquettes

Lors de la seconde invocation, ls a reu un unique argument constitu de la chane "script_1.sh script_2.sh". Il nous faudrait donc un paramtre spcial, qui, lorsquil se dveloppe entre guillemets, fournisse autant darguments quil y en avait lorigine, mais en protgeant chacun deux par des guillemets. Ce paramtre existe et il est not $@. Lorsquil nest pas encadr

valuation dexpressions CHAPITRE 3

67

de guillemets, il se comporte exactement comme $*. Sinon, il se dveloppe comme nous le souhaitons :
ll.sh: 1 2 #! /bin/sh ls -l "$@"

Cette fois-ci, le script agit comme nous le voulons :


$ ./ll.sh c* -rwxr-xr-x 1 cpb -rwxr-xr-x 1 cpb dr-xr-xr-x 34 ccb $ cpb cpb ccb 560 Oct 4 13:23 script_1.sh 551 Oct 17 15:54 script_2.sh 8192 Aug 6 2006 /mnt/dos/Program Files

$ ./ll.sh -d /mnt/dos/P*

Ainsi, dans la plupart des cas, lorsquun programme devra manipuler lensemble de ces arguments, il sera prfrable demployer le paramtre "$@" plutt que $*. Dautres paramtres spciaux sont disponibles, comme $$, $! ou $?, que nous tudierons ultrieurement.

Protection des expressions


Il peut arriver que certaines expressions possdent des caractres qui ont une signication particulire pour le shell, et que nous ne souhaitions pas quils soient interprts par ce dernier. Par exemple, afcher le prix amricain $5 nest pas si facile :
$ echo $5 $ echo "$5" $

En effet, le shell peut en dduire que nous voulons afcher le contenu du cinquime paramtre positionnel, vide en loccurrence. De mme, le symbole # sert introduire un commentaire qui stend jusqu la n de la ligne, et le sens dun message peut en tre modi :
$ echo en Fa # ou en Si ? en Fa $

68

Shells Linux et Unix par la pratique

Une autre surprise attend le programmeur qui veut raliser un joli cadre autour du titre de son script :
$ echo ******************** affiche_arguments.sh affiche_arguments_2.sh affiche_arguments_3.sh affiche_arguments_4.sh affiche_arguments_5.sh affiche_arguments_6. sh extraction_serveurs.sh ll .sh ll_1.sh ll_2.sh message.news var_locales.sh $

Le caractre * remplace nimporte quelle chane dans un nom de chier. La commande echo reoit donc en argument la liste des chiers du rpertoire courant.
Echo en substitut ls Comme echo est gnralement une commande interne du shell, cette fonctionnalit permet de consulter le contenu dun rpertoire sans accder /bin/ls. Bien des administrateurs lont utilise pour accder un systme sur lequel ils venaient par erreur de dtruire /bin, alors quun shell tait toujours connect.

La solution adquate consiste protger le caractre spcial de linterprtation du shell. Cela peut seffectuer de plusieurs manires. On peut ainsi utiliser le caractre backslash (barre oblique inverse) \, les apostrophes '', ou encore les guillemets "".

Protection par le caractre backslash


Ce caractre sert dsactiver linterprtation du caractre quil prcde. Ainsi, on peut crire :
$ echo \$5 $5 $ echo Fa \# ou Do \# Fa # ou Do # $ echo \*\*\* *** $

Le caractre backslash peut servir prxer nimporte quel caractre, y compris luimme :
$ echo un \\ prcde \$5 un \ prcde $5 $

valuation dexpressions CHAPITRE 3

69

Lorsquil prcde un retour chariot, le backslash a pour effet de lliminer et la saisie continue sur la ligne suivante.
$ echo dbut \ > et fin. dbut et fin. $

Protection par apostrophes


La protection des expressions par un backslash qui prcde chaque caractre spcial est parfois un peu fastidieuse et on lui prfre souvent le mcanisme de protection par des apostrophes, qui permet de manipuler toute lexpression en une seule fois. Entre des apostrophes, tous les caractres rencontrs perdent leur signication spciale. Cela signie que le backslash est un caractre comme les autres, et que lon ne peut pas lemployer pour protger une apostrophe. Le seul caractre qui ne puisse pas tre inclus dans une chane protge par des apostrophes est donc lapostrophe elle-mme.
$ echo #\$"&>| #\$"&>| $

Lorsquun retour chariot est prsent dans une chane entre apostrophes, la reprsentation du code est inchange :
$ echo dbut > milieu > et fin dbut milieu et fin $

Protection par guillemets


La protection par des apostrophes est totale, chaque caractre gardant sa signication littrale (on parle de protection forte). Il arrive pourtant que lon prfre quelque chose dun peu moins strict. La protection par les guillemets est plus adapte dans ce cas,

70

Shells Linux et Unix par la pratique

puisquelle permet de conserver lunit dune chane de caractres sans fragmentation en diffrents mots. Entre les guillemets, les caractres $, apostrophe et backslash retrouvent leur signication spciale, alors que les autres sont rduits leur acception littrale. En fait, le backslash et le $ nont un sens particulier que sils sont suivis dun caractre pour lequel linterprtation spciale a un sens. Voyons quelques exemples :
$ A="ABC DEF" $ B="$A $ \$A \ \" " $ echo $B ABC DEF $ $A \ " $

Dans laffectation de la variable B, le premier $ est suivi de A; lvaluation fournit le contenu de la variable A. Le deuxime $ est suivi par un blanc et une interprtation autre que littrale naurait pas de sens. Le troisime $ est prcd dun backslash, il na donc pas de sens particulier et lon afche les deux caractres $A. Le backslash suivant prcde un espace, la seule interprtation est donc littrale. Comme le premier guillemet est prcd dun backslash, il est littral et ne sert pas fermer la chane ; il est donc afch. Le second guillemet, en revanche, clt lexpression. Les guillemets sont trs utiles pour prserver les espaces, tabulations et retours chariot dans une expression. Nous en avions vu un exemple plus haut, lorsque nous affections le rsultat de la commande ls une variable. Si on avait voulu afcher le contenu de cette variable en conservant les retours la ligne, il aurait fallu empcher le shell de procder la sparation des arguments et leur remise en forme, spars par des espaces. On aurait donc encadr la variable par des guillemets :
$ var=$(ls /dev/hda1*) $ echo $var /dev/hda1 /dev/hda10 /dev/hda11 /dev/hda12 /dev/hda13 /dev/hda14 /dev/hda15 /dev/hda16 $ echo "$var" /dev/hda1 /dev/hda10 /dev/hda11 /dev/hda12 /dev/hda13

valuation dexpressions CHAPITRE 3

71

/dev/hda14 /dev/hda15 /dev/hda16 $

Nous avons vu que linterprtation du paramtre spcial $@, lorsquil est encadr par des guillemets, est particulire. ce propos, nous avons indiqu quil vaut gnralement mieux utiliser "$@" que $*, mais cela est galement vrai pour des paramtres isols. Lorsquun script emploie une variable dont la valeur est congure par lutilisateur (paramtres positionnels, variable denvironnement, etc.), la prudence envers son contenu est de rigueur, car des caractres blancs peuvent sy trouver, perturbant son interprtation par le shell. Ainsi, on prfrera encadrer systmatiquement ces paramtres par des guillemets, comme cela a t fait dans le script rm_secure.sh du prcdent chapitre, et plus particulirement pour sa variable $sauvegarde_rm. En conclusion, la protection par des guillemets est la plus utilise, peut-tre parce quelle rappelle la manipulation des chanes de caractres dans les langages comme le C. Elle est trs commode, car elle conserve lvaluation des variables, tout en prservant la mise en forme de lexpression. On emploie la protection par apostrophes lorsque le respect strict du contenu dune chane est ncessaire. Il faut faire attention ne pas confondre cette protection avec lencadrement par des apostrophes inverses `` que lon rencontre dans une forme ancienne dinvocation de commande, laquelle on prfre de nos jours $(). Nous encourageons vivement le lecteur exprimenter les diverses formes de protection, en incluant dans ses chanes des caractres spciaux ($, &, <, ", etc.).

Tableaux
Nous navons voqu, jusqu prsent, que des variables scalaires. Les shells rcents permettent toutefois de manipuler des tableaux qui contiennent autant de donnes quon le souhaite (la seule limitation tant celle de la mmoire du systme). Nous avons repouss la prsentation des tableaux la n de ce chapitre, car ils ne prsentent aucune difcult dassimilation, et nous allons rapidement voir leurs particularits par rapport aux constructions tudies jusquici. La notation :
tableau[i]=valeur

a pour effet de dnir un emplacement mmoire pour la ie case du tableau, et de la remplir avec la valeur voulue. La consultation se fera ainsi :
${tableau[i]}

Les accolades sont obligatoires pour viter les ambiguts avec ${tableau}[i].

72

Shells Linux et Unix par la pratique

Les index des tableaux sont numrots partir de zro. En fait, ${tableau[0]} est identique $tableau. Cela signie donc que nimporte quelle variable peut tre considre comme le rang zro dun tableau qui ne contient quune case. On peut aussi ajouter autant de membres quon le dsire une variable existante :
$ var="valeur originale" $ echo $var valeur originale $ echo ${var[0]} valeur originale $ var[1]="nouveau membre" $ echo ${var[0]} valeur originale $ echo ${var[1]} nouveau membre $

Les notations ${table[*]} et ${table[@]} fournissent une liste de tous les membres du tableau. Places entre guillemets, ces deux notations prsentent les mmes diffrences que $* et $@ pour les paramtres positionnels. ${#table[i]} fournit la longueur du ie membre du tableau, alors que ${#table[*]}, comme ${#table[@]}, fournissent le nombre de membres :
$ echo ${#var[@]} 2 $ echo ${#var[*]} 2 $ echo ${#var[0]} 16 $ echo ${#var[1]} 14 $

valuation explicite dune expression


Lune des grosses diffrences entre les langages compils et les langages interprts est quil est gnralement possible, avec ces derniers, de construire de toutes pices une expression, puis de lvaluer comme sil sagissait dune partie du programme en cours. Ce mcanisme est trs utile dans les langages dintelligence articielle comme Lisp.

valuation dexpressions CHAPITRE 3

73

Le shell propose une fonction nomme eval qui permet dobtenir ce comportement. Elle sert rclamer au shell une seconde valuation des arguments qui lui sont transmis. Pour bien comprendre ce que cela signie, il nous faut procder par tapes successives. Tout dabord, on dnit une variable A, et une variable B contenant une chane de caractres dans laquelle on trouve lexpression $A.
$ A="Contenu de A" $ B=" A = \$A" $ echo $B A = $A $

Lors de laffectation de B, nous avons protg le caractre $ pour quil ne soit pas vu comme une consultation du contenu de A. Examinons prsent ce qui se passe lorsquon invoque eval avant echo:
$ eval echo $B A = Contenu de A $

En fait, lorsque le shell interprte la ligne evalecho$B, il remplace $B par le contenu de la variable, puis invoque eval en lui passant en arguments echo, A, = et $A. Le travail deval consiste alors regrouper ses arguments en une seule ligne echoA=$A quil fait rvaluer par le shell. Celui-ci remplace alors $A par son contenu et afche le rsultat attendu. Il faut bien comprendre que lvaluation de $A tant retarde, le contenu de cette variable nest pas inscrit dans la variable B. Si on modie A et que lon interroge nouveau $B au travers dun eval, on obtient :
$ eval echo $B A = Contenu de A $ A="Nouveau contenu" $ eval echo $B A = Nouveau contenu $

74

Shells Linux et Unix par la pratique

Les implications sont trs importantes, car nous sommes mme de construire dynamiquement, au sein dun script, des commandes dont nous demandons lvaluation au shell :
$ commande="echo \$B" $ echo $commande echo $B $ eval $commande A = $A $

On peut mme imbriquer un appel eval au sein de la chane value :


$ commande="eval echo \$B" $ echo $commande eval echo $B $ eval $commande A = Nouveau contenu $

Le petit script suivant est une reprsentation trs simplie de la boucle dinterprtation du shell. Il lit (avec la fonction read que nous verrons dans le prochain chapitre) la saisie de lutilisateur dans sa variable ligne et en demande lvaluation au shell. Si la saisie est vide, le script se termine.
mini_shell.sh: 1 2 3 4 5 6 7 8 9 10 #! /bin/sh while true ; do echo -n "? " read ligne if [ -z "$ligne" ] ; then break; fi eval $ligne done

valuation dexpressions CHAPITRE 3

75

Son fonctionnement est naturellement trs simple, mais nanmoins intressant :


$ ./mini_shell.sh ? ls affiche_arguments.sh affiche_arguments_6.sh ll_1.sh ll_2.sh message.news

affiche_arguments_2.sh calcule_paques.sh affiche_arguments_3.sh calcule_paques_2.sh

affiche_arguments_4.sh extraction_serveurs.sh mini_shell.sh affiche_arguments_5.sh ll.sh ? A=123456 ? echo $A 123456 ? B=A = $A ? echo $B A = $A ? eval echo $B A = 123456 ? $ (Entre) var_locales.sh

Conclusion
Ce chapitre nous a servi mettre en place les principes de lvaluation des expressions par le shell. Nous pouvons prsent tudier les constructions propres la programmation, ainsi que les commodits offertes par Bash ou Ksh pour crire des scripts puissants.

Exercices
3.1 Majuscules et minuscules dans les chiers (facile)
crivez un script qui prend une liste de chiers en argument et qui appelle la commande tr, avec les ensembles [[:upper:]] et [[:lower:]], pour passer le contenu des chiers en majuscules. Attention, il faut toujours travailler avec un chier temporaire, ne jamais utiliser le mme chier en redirection dentre et de sortie. crivez ensuite le script symtrique qui transforme le contenu des chiers en minuscules.

76

Shells Linux et Unix par la pratique

3.2 Majuscules, minuscules et noms de chiers (facile)


crivez deux scripts sur le modle de lexercice prcdent qui convertissent les noms des chiers mais pas leur contenu.

3.3 Arithmtique et invocation de commande (plutt facile)


crivez un script qui appelle la commande cal pour afcher un calendrier de lanne prochaine. Conseil : utilisez la commande date avec un argument de formatage pour obtenir lanne courante.

3.4 Extractions de motifs (moyen)


Votre script devra prendre en argument un nom de chier complet (avec son chemin daccs) et afcher successivement le nom seul et le chemin seul. Utilisez pour cela les extractions de prxe ${...#...} et de sufxes ${...%...}. Nappelez pas les commandes basename et dirname qui effectuent directement ce travail !

3.5 Contournement des apostrophes (difcile)


Crez un programme capable dcrire son propre code source sur sa sortie standard. Il en existe des versions dans de nombreux langages, et la cration dun tel script pour le shell est un d amusant, essentiellement en raison de limpossibilit dinclure une apostrophe, mme protge par un backslash dans une expression encadre par des apostrophes. Un algorithme possible est dcrit dans [Hofstadter 1985] Gdel, Escher, Bach Les Brins dune guirlande ternelle. On se reportera ce livre fameux de Douglas Hofstadter pour comprendre en dtail lintrt intellectuel dun tel mcanisme.

4
lments de programmation shell
Ce chapitre va nous permettre dtudier les lments de base de la programmation de scripts pour le shell. Nous y verrons les enchanements parfois complexes de commandes, ainsi que les redirections dentres-sorties dans leurs formes simples et avances. Nous avons dj rencontr quelques structures de contrle, comme les tests ou les boucles ; nous en ferons linventaire complet ici.

Commandes et code de retour


Commande simple
Lopration la plus lmentaire que lon puisse effectuer avec le shell est appele commande simple. Elle est compose en thorie daffectations de variables suivies dune squence de mots, elle-mme suivie doprateurs de redirection. Par chance, chaque lment de cette commande prtendue simple est facultatif. Dans la pratique donc, il sagit soit dune affectation de variables, soit dune squence de mots (le premier tant la commande excuter), ventuellement suivie doprateurs de redirection. Lorsquune affectation de variable prcde la commande (sur la mme ligne), le contenu de la variable ne prend la valeur indique que pendant lexcution de la commande.

78

Shells Linux et Unix par la pratique

Ce mcanisme, bien que rarement employ, permet de modier une variable denvironnement pendant lexcution dune commande, sans changer sa valeur pour le reste du script. Voici quelques exemples de commandes simples :
$ A=1 $ echo $A 1 $ A=1 B=2 C=$(($A + $B)) $ echo $C 3 $ date lun oct 23 09:53:57 CEST 2000 $ LANG= $ find . -name test.sh -exec chmod 755 {} \;

Vrions laffectation temporaire de variable pendant lexcution dune commande :


$ echo $LANG fr_FR $ date dim oct 7 08:55:51 CEST 2007 $ LANG=en_US date Sun Oct 7 08:55:54 CEST 2007 $ LANG=de_DE date So 7. Okt 08:55:57 CEST 2007 $ LANG=it_IT date dom ott 7 08:56:00 CEST 2007 $ echo $LANG fr_FR $

Lorsquil se termine, un processus sous Unix renvoie toujours un code de retour. Il sagit dune valeur entire qui est disponible pour son processus pre. Cette valeur peut tre consulte, laide des instructions de test que nous verrons plus bas. Lors de lexcution dune commande simple, le code de retour est celui de linstruction invoque. Si la commande simple est une affectation, le code de retour est nul.

lments de programmation shell CHAPITRE 4

79

Pipelines
Il est souvent intressant denvoyer la sortie dune commande simple directement en entre dune autre commande. On obtient cela en les couplant par loprateur |, nomm habituellement tube (pipe), qui construit ainsi un pipeline. On peut enchaner autant de commandes simples que lon veut, chacune agissant la manire dun ltre pour traiter les donnes fournies par la prcdente. Ce mcanisme correspond aux principes fondamentaux des systmes Unix o lon peut faire cooprer des utilitaires spcialiss, chacun deux tant trs efcace pour une tche donne. Par exemple, le pipeline suivant sert afcher lespace libre (en kilo-octet) sur la partition qui contient le rpertoire de travail :
$ df -k . | tail -1 | sed "s/ */ /g" | cut -d " " -f 4 699802 $

Dtaillons son fonctionnement : df afche des statistiques en kilo-octets (option -k) sur le rpertoire courant (point .), comme ceci :
$ df -k . Filesystem /dev/hda5 $ 1k-blocks 3616949 Used Available Use% Mounted on 2730039 699802 80% /

Pour liminer la ligne den-tte, nous transfrons le rsultat en entre de la commande


tail laquelle nous demandons (par son option -1) de nafcher quune seule ligne en

partant de la n. En voici le rsultat :


$ df -k . | tail -1 /dev/hda5 $ 3616949 2730039 699802 80% /

Le champ qui nous intresse est le quatrime. Nous lextrairons en utilisant la commande
cut (et son option -f4) ; toutefois, il faut connatre le sparateur de champs. Ici, il sagit de lespace (indiqu par loption -d"" de cut). Avant cela, il faut liminer les occurren-

ces successives de lespace en les remplaant par un seul caractre. Cest le rle jou par la commande sed laquelle nous fournissons une chane de caractres "s/*//g" qui demande ce remplacement (les scripts sed seront tudis dans un chapitre ultrieur). La sortie de cette commande est donc :
$ df -k . | tail -1 | sed "s/ */ /g" /dev/hda5 3616949 2730039 699802 80% / $

80

Shells Linux et Unix par la pratique

Linvocation nale de cut permet de ne conserver que le quatrime champ :


$ df -k . | tail -1 | sed "s/ */ /g" | cut -d " " -f 4 699802 $

Il peut paratre trange dtre oblig dinvoquer successivement quatre commandes pour obtenir un rsultat somme toute assez simple, mais il ne faut pas oublier que chacune de ces quatre tapes est remplie par un utilitaire trs performant et trs able, capable de sadapter des situations trs diffrentes. Notez que Awk, que nous tudierons dans un autre chapitre, permet de regrouper avantageusement les trois dernires tapes de notre pipeline. Chaque commande simple est excute par un processus indpendant et tous les processus se droulent simultanment. Chaque processus attend que son prdcesseur dans le pipeline lui envoie des donnes et les traite ds quelles sont disponibles, puis transmet ses rsultats son successeur. Bien sr, une commande comme tail, qui afche les dernires lignes de son entre standard, est oblige dattendre que son prdcesseur ait ni denvoyer toutes ses donnes avant de travailler mais, dans la plupart des cas, les actions ont lieu simultanment. Lexemple le plus simple en est donn par le pipeline cat|cat. La premire commande simple cat copie son entre standard (le clavier du terminal) vers sa sortie standard (le tube). La seconde copie son entre standard (le tube) vers sa sortie standard (lcran du terminal). Comme le terminal afche galement un cho des touches frappes, nous voyons les lignes se rpter au fur et mesure de la saisie :
$ cat | cat premire ligne premire ligne deuxime deuxime troisime et dernire troisime et dernire (Contrle-D) $

Nous remarquons deux choses : La saisie se fait ligne par ligne. Cest une question de conguration du terminal, que nous pouvons modier en employant loption -icanon de la commande stty. Cela nentre pas dans le cadre de notre propos ; on remarquera simplement que la lecture des donnes depuis le premier cat a lieu par lignes entires. Pour terminer la saisie, on presse la touche Contrle-D, qui signie n de chier (abrvi souvent EOF, pour End Of File). Le premier cat se termine alors, et ferme sa

lments de programmation shell CHAPITRE 4

81

sortie standard, ce que le second cat peroit comme une n de chier galement, avant de se terminer son tour.
Figure 41

Excution du tube cat | cat

Nous voyons bien dans cet exemple que les deux processus progressent paralllement, les donnes dentre de lun tant combines avec les donnes de sortie de lautre. Le code de retour dun pipeline est celui de sa dernire commande.

Listes de pipelines
On peut combiner plusieurs pipelines dans une mme liste de commandes. Il sagit ainsi dorganiser les connexions entre plusieurs oprations. Quatre symboles peuvent tre utiliss pour sparer les oprations, et leurs signications respectives sont indiques dans le tableau suivant.
Symbole Connexion
Squencement

Dtail
La seconde opration ne commence quaprs la n de la premire. Le point-virgule peut tre remplac par un retour chariot, ce qui correspond la prsentation habituelle des scripts. La premire opration est lance larrire-plan, et la seconde dmarre simultanment ; elles nont pas dinteractions entre elles. La seconde opration nest excute que si la premire a renvoy un code de retour nul, ce qui, par convention, signie succs . La seconde opration nest excute que si la premire a renvoy un code de retour non nul, ce qui, par convention, signie chec .

& && ||

Paralllisme Dpendance Alternative

Excutions squentielles

Nous allons voir quelques exemples quil est assez difcile de dmontrer par crit, aussi le lecteur est-il encourag tester les oprations pour bien observer lenchanement des oprations. Nous commenons par le squencement avec le point-virgule, relation la plus

82

Shells Linux et Unix par la pratique

courante, puisquil sagit dexcuter les commandes une une en attendant chaque fois que la prcdente soit termine.
$ echo dbut ; sleep 5 ; echo milieu ; sleep 5 ; echo fin dbut (5 secondes scoulent) milieu ( nouveau 5 secondes) fin $

On notera que la seconde opration commence quelle que soit la manire dont la prcdente sest termine. Par exemple, la commande false est un utilitaire qui renvoie toujours un code de retour indiquant un chec (pour tester des scripts par exemple), tandis que true est un outil renvoyant toujours un code de russite. Nous pouvons vrier que la russite ou lchec dune commande ninue pas sur la suivante :
$ echo dbut ; false ; echo fin dbut fin $ echo dbut ; true ; echo fin dbut fin $

Il y a une divergence de comportement entre Bash et Ksh lorsquune opration est interrompue par un signal. Avec Bash en mode interactif, le processus correspondant est annihil, mais lopration suivante a lieu normalement. Pour preuve, nous envoyons le signal SIGINT en pressant la touche Contrle-C durant lexcution de la commande sleep, ce qui a pour effet de la tuer . Nous vrions que la commande echo ultrieure a bien lieu.
$ echo dbut ; sleep 20 ; echo fin dbut (Contrle-C) fin $

Avec Ksh, le droulement est diffrent, linterruption dune commande termine toute la squence :
$ echo dbut ; sleep 20 ; echo fin dbut (Contrle-C) $

lments de programmation shell CHAPITRE 4

83

Quoiquil en soit, cette divergence de comportement ne concerne que lexcution interactive dune squence de commandes. Si les commandes sont enchanes au sein dun script, et que lon presse Contrle-C, lexcution du script complet est immdiatement interrompue.
Excutions parallles

Le second type de connexions entre les pipelines est le paralllisme, cest--dire les excutions simultanes des commandes, sans synchronisation. Le shell lance le premier processus larrire-plan ; il na donc en principe pas accs au terminal. Lcriture sur ce terminal sera gnralement tolre avec des restrictions que nous verrons plus bas mais la lecture y est impossible. Le dernier pipeline de la liste est excut au premier plan. Nous crons un script qui afche cinq fois son premier argument, avec des sommeils dune seconde entre chaque afchage.
boucle_arg.sh: 1 2 3 4 5 6 7 #! /bin/sh i=0 while [ $i -lt 5 ] ; do echo $1 i=$((i + 1)) sleep 1 done

Nous allons invoquer ce script deux reprises, en les sparant par loprateur &, et vrier que les excutions sont bien simultanes.
$ ./boucle_arg.sh 1 & ./boucle_arg.sh 2 [1] 6437 1 2 1 2 1 2 1 2 1 2 [1]+ Done ./boucle_arg.sh 1 $

Nous remarquons que Bash afche une premire ligne inattendue : [1]6437. Il sagit en ralit dun message envoy sur la sortie derreur standard du processus (qui correspond par dfaut lcran du terminal). Ce message ne reprsente dailleurs pas une

84

Shells Linux et Unix par la pratique

erreur mais simplement une information signicative pour lutilisateur. Elle indique que la commande qui est dote du numro de processus 6437 passe larrire-plan, et que le numro de job 1 lui est attribu. Symtriquement, lorsque cette commande se termine, Bash afche la ligne [1]+Done, suivie du nom du programme excut. Nous pouvons bien vrier que limbrication des deux boucles est correcte, les processus sexcutant simultanment. Il est alors possible de se livrer une exprience intressante en tuant le pipeline en avant-plan avec Contrle-C, alors que celui qui est larrireplan continuera se drouler. Pour pimenter un peu laction, nous lanons trois boucles en parallle.
$ ./boucle_arg.sh 1 & ./boucle_arg.sh 2 & ./boucle_arg.sh 3 [1] 6462 [2] 6463 1 2 3 1 2 3 (Contrle-C) $ 1 2 1 2 1 2

Nous voyons quil y a bien deux jobs larrire-plan et que les afchages sont bien entremls. Lorsque nous pressons Contrle-C, le processus lavant-plan reoit le signal SIGINT qui le tue. Il sagit de la boucle 3, qui disparat alors. Comme le shell na plus de job lavant-plan, il afche son symbole dinvite ($). Malgr tout, les deux autres commandes continuent boucler larrire-plan et afchent leurs messages rgulirement, ce qui explique la squence de 1 et de 2 persistante, alors que le shell attend une saisie. Si un processus larrire-plan essaie de lire sur son terminal, un signal SIGTTIN linterrompra sans le tuer pour autant (pour le rveiller, il faudra lui envoyer un signal SIGCONT ou le ramener au premier plan avec la commande fg foreground). Dans de nombreux cas, on ne peut pas tolrer quun processus larrire-plan puisse crire sur lcran du terminal en mme temps que le processus lavant-plan, venant perturber lafchage. Pour viter cela, on peut congurer le terminal an quil envoie un

lments de programmation shell CHAPITRE 4

85

signal SIGTTOU tout processus larrire-plan qui essaie dcrire sur lcran. Ce signal interrompt le processus sans le tuer, et on peut le rveiller ensuite. Pour congurer de cette faon le terminal, il faut employer loption tostop de la commande stty. On la replacera dans son tat par dfaut avec loption tostop. Une fois que le processus sera suspendu, on le rveillera en le ramenant lavant-plan avec la commande fg.
$ stty tostop $ ./boucle_arg.sh 1 & ./boucle_arg.sh 2 [1] 6500 2 2 2 2 2 [1]+ Stopped (tty output) $ fg ./boucle_arg.sh 1 1 1 1 1 1 $ stty -tostop $ ./boucle_arg.sh 1

Lorsquune liste ne comportant ventuellement quun seul lment se termine par le symbole &, le shell lance tous les pipelines larrire-plan et reprend immdiatement la main pour afcher son symbole dinvite et attendre une saisie. En revanche, il attendra une pression sur la touche Entre avant dafcher le message [1]+Done la n du processus.
$ date & [1] 6595 $ mar oct 24 11:07:23 CEST 2000 (Entre) [1]+ Done $ date

86

Shells Linux et Unix par la pratique

Les commandes internes fg et bg sont conues pour tre employes dune manire interactive. On peut leur fournir en argument un numro de job. fg, nous lavons vu, permet de ramener au premier plan un processus qui sexcutait en arrire-plan. Sil est suspendu, il reoit automatiquement le signal SIGCONT qui le relance. bg sert basculer un job en arrire-plan, comme sil tait suivi dun & dans une liste de commandes. En pratique, bg ne sert quavec des jobs suspendus (soit par une tentative de lecture, voire, dans certains cas, dcriture sur le terminal, soit par un signal SIGTSTP mis avec la squence de touches Contrle-Z alors que le processus tait en avant-plan).
$ ./boucle_arg.sh 1 1 1 (Contrle-Z) [1]+ Stopped $ fg ./boucle_arg.sh 1 1 1 1 $ ./boucle_arg.sh 1 1 1 1 (Contrle-Z) [1]+ Stopped $ bg [1]+ ./boucle_arg.sh 1 & $ 1 1 (Entre) [1]+ Done $ ./boucle_arg.sh 1 ./boucle_arg.sh 1 ./boucle_arg.sh 1

Lorsquune commande suivie du symbole & est lance larrire-plan par le shell, un code de retour nul (russite) est immdiatement renvoy. Le vritable code de retour fourni par le processus ne pourra tre rcupr que par lintermdiaire de la commande wait. Le numro didentication (PID) du dernier processus lanc larrire-plan est disponible dans un paramtre spcial not $!. Nous reviendrons sur lutilisation de &, $!, ainsi que de la commande interne wait plus avant.

lments de programmation shell CHAPITRE 4

87

Excutions dpendantes

Lorsque deux pipelines sont spars par le symbole &&, lexcution du second est assujettie la valeur de retour du premier. Si le premier pipeline renvoie une valeur nulle, signiant par convention russite , alors le shell excute le second ; sinon, ce dernier est ignor. Ainsi la ligne gcc exemple.c -o exemple && ./exemple demande-t-elle la compilation par gcc dun chier source exemple.c pour obtenir lexcutable exemple puis, si la compilation se droule sans erreur, le lancement du programme compil. Cela peut donner des listes assez longues, comme dans le script suivant :
test_et_logique.sh: echo read echo read grep echo -n "Fichier examiner : " && F && -n "Texte recherch : " && T && $T $F > /dev/null && "Le texte $T a t trouv"

Les commandes read et grep seront dtailles ultrieurement.


$ ./test_et_logique.sh Fichier examiner : *.sh Texte recherch : absent $ ./test_et_logique.sh Fichier examiner : *.sh Texte recherch : /dev/null Le texte /dev/null a t trouv $

La valeur de retour dune liste lie par && est celle renvoye par le dernier pipeline excut. On peut considrer quil sagit dun moyen de lier par un ET logique les codes de retour des pipelines, mme si certains dentre eux ne sont pas excuts (ds quun pipeline choue, lexcution de la liste sarrte). On peut employer cet oprateur pour lier logiquement des conditions dans un test if-then-fi. Par exemple :
if dans_secteur $i $x $y && secteur_en_alerte $i ; then declencher_alarme_sur_secteur $i fi

tant donn quune liste connecte par des && est interrompue ds quune opration choue, certains lemploient pour rendre lcriture des tests plus concise, en remplaant compltement la construction if-then-fi. La lisibilit en est tantt amliore lorsque des tests similaires se rptent sur des lignes successives tantt dgrade ; il faut choisir avec soin lendroit o il convient demployer ce type denchanement. Par exemple, la commande interne [] est un test qui renvoie zro (vrai) ou une autre valeur (faux) en fonction des paramtres et des options qui lui sont transmises. On peut

88

Shells Linux et Unix par la pratique

donc la faire suivre dun &&, et dune commande qui ne sera excute que si le test russit. Loption -lt (lesser than) signie infrieur strictement .
$ A=1 $ [ $A -lt 2 ] && echo vrai vrai $ A=3 $ [ $A -lt 2 ] && echo vrai $

Nous obtenons le mme rsultat que :


if [ $A -lt 2 ] ; then echo vrai fi

Si plusieurs tests doivent tre imbriqus, ou sil faut excuter plusieurs instructions conditionnes par le test, la construction if-then-fi est probablement plus lisible. Dun autre ct, une fois que lon a assimil lutilisation de && en remplacement dun test, on peut apprcier la concision dune squence :
[ [ [ [ $T $T $T $T -lt -gt -lt -gt $Seuil_bas $Seuil_haut $Seuil_mini $Seuil_maxi ] ] ] ] && && && && regulation +1 regulation -1 alarme "Temprature trop basse" alarme "Temprature trop leve"

Excutions optionnelles

Symtriquement, il existe une connexion || avec laquelle la seconde partie de la liste de pipelines nest excute que si la premire partie a chou (en renvoyant une valeur non nulle). La liste sinterrompt donc ds quune opration russit. Le code de retour renvoy est celui du dernier pipeline excut. On peut considrer cette connexion comme un OU logique entre les codes de retour des pipelines, tout en conservant lesprit que les derniers dentre eux ne sont pas toujours excuts. On lutilise surtout dans deux cas : lier logiquement les conditions dun test if-then-fi, et dclencher des actions sur erreur. La liaison par un OU logique sobtient naturellement :
if [ $r -lt $r_min ] || [ $g -lt $g_min ] || [ $b -lt $b_min ] then echo "Niveau dencre insuffisant" fi

Lorsquune opration cruciale choue dans un script, il faut gnralement afcher un message derreur, et interrompre le processus. On peut employer || pour cela :
ping -c 1 $host || { echo "$host nest pas accessible" ; exit 1 }

lments de programmation shell CHAPITRE 4

89

Ce type de construction permet damliorer la robustesse dun script, comme nous le verrons ultrieurement.

Commandes composes
Lexemple prcdent fait apparatre un regroupement de commandes entre accolades {} que lon nomme commande compose . Il sagit simplement dune construction qui permet de considrer plusieurs oprations comme un seul bloc de commandes. Cela sert surtout grer les priorits des relations entre pipelines. Si les accolades taient absentes de lexemple prcdent, || ayant prcdence sur ;, la commande exit1 serait toujours invoque mme lorsque la commande ping russit son action. Les commandes composes ont une autre utilit : elles permettent dappliquer une redirection globale des entres-sorties sur un ensemble doprations, comme nous le verrons plus bas. Il faut bien noter quil doit y avoir des espaces entre les accolades et les commandes quelles encadrent. Ce caractre nest pas un sparateur comme pouvaient ltre &, && ou ||, et ne doit donc pas tre juxtapos un nom de commande. Il faut bien distinguer les commandes composes, que lon encadre par des accolades, des cas dutilisation de ce symbole pour dvelopper des chanes de caractres partir dune liste dlments spars par des virgules, comme dans lexpression :
cp /var/log/{cron,maillog,messages,secure} sauvegarde/

Une autre structure permet de dnir des commandes composes : le regroupement entre parenthses (). Son comportement est en revanche totalement diffrent. Les pipelines sont alors excuts dans un sous-shell. Un nouveau processus est cr et donc toute modication de lenvironnement apporte dans le regroupement entre parenthses naura plus aucun effet une fois extrait de cette structure. Il existe toutefois une diffrence notable par rapport aux scripts : les variables du shell mme celles qui nont pas t explicitement exportes sont disponibles pour le sous-shell. Vrions quune variable non exporte (B en loccurrence) est disponible, mais que les modications ninuent pas sur lenvironnement du shell pre :
$ A=1 ; B=2 ; export A $ echo Sh1 $A $B; (echo Sh2 $A $B; A=3; B=4) ; echo Sh1 $A $B Sh1 1 2 Sh2 1 2 Sh1 1 2 $

Nous remarquons qu la diffrence des accolades, les parenthses peuvent tre accoles aux commandes quelles encadrent ! Les deux types de commandes composes {} et () renvoient le code de retour de la liste de commandes excute.

90

Shells Linux et Unix par la pratique

Redirections dentres-sorties
La redirection des entres-sorties dun processus est lune des fonctionnalits essentielles des systmes de type Unix et le shell est loutil le plus simple pour mettre en place ces redirections.

Entres-sorties standards
Par convention, lorsquun processus dmarre, il dispose de trois descripteurs de chiers des canaux de communication dj ouverts. Ces descripteurs sont congurs par le processus pre mais pas par le noyau ; il sagit bien dune simple convention, pas dun fonctionnement intimement li au fonctionnement du systme. Le premier descripteur (numro 0) est ouvert en lecture, et dirig par dfaut vers le clavier du terminal. On lappelle entre standard du processus. Le deuxime descripteur (numro 1), ouvert en criture, est la sortie standard du processus, congure par dfaut vers lcran du terminal. Enn, le troisime descripteur (numro 2) est ouvert en criture galement vers lcran ; il est appel sortie derreur standard.
Figure 42

Descripteurs des entres-sorties standards

Lentre standard est utilise pour obtenir les donnes sur lesquelles un processus va travailler. Lorsquil sagit du terminal, le descripteur est ouvert, en ralit, en lecture et en criture. Lorsquil sagit dune redirection depuis un chier, ce dernier est ouvert en lecture seule. Dans un script shell, lentre standard peut tre consulte au moyen de la commande read, que nous dtaillerons plus bas et qui remplit une variable avec une chane de caractres lue depuis le clavier. La sortie standard sert afcher les rsultats du programme. On peut trs bien la rediriger dans un chier ou dans un tube vers lentre standard dune autre commande. Dans les scripts, on crit sur la sortie standard avec la commande echo. La sortie derreur a pour fonction dafcher immdiatement les messages de diagnostic relatifs au fonctionnement du programme, plutt que ses rsultats proprement dits. Par dfaut, ces messages sont envoys vers le terminal, comme la sortie standard. Si cette

lments de programmation shell CHAPITRE 4

91

dernire est redirige vers un tube ou un chier, les messages derreur restent en revanche sur lcran. Les deux descripteurs sont bien distincts. La sortie derreur standard peut tre utilise dans les scripts shell au moyen dune redirection particulire que nous verrons plus bas.

Redirection des entres et sorties standards


La premire faon de procder pour rediriger les entres-sorties des processus, cest dutiliser loprateur tube (pipe) | pour crer des pipelines, comme nous lavons dj vu. Dans ces conditions, la sortie standard dune commande est directement connecte lentre standard de la suivante. Pour tester cette opration, nous allons tenter dcrire avec echo un message dans un tube, puis de ly relire avec read.
Figure 43

Redirection echo | read

Faisons un essai avec Ksh :


$ echo "Mon message" | read VAR $ echo $VAR Mon message $

Parfait. Essayons prsent avec Bash :


$ echo "Mon message" | read VAR $ echo $VAR $

Apparemment, cela ne fonctionne pas ! En fait, en rchissant un peu sur le principe des pipelines, on comprend que chaque commande est lance dans un processus ls et que, dans celui-ci, la modication dune variable denvironnement (la variable VAR remplie par la commande read) na aucune inuence sur lenvironnement du processus pre (le shell

92

Shells Linux et Unix par la pratique

interactif). Pour que notre exprience russisse, il faut que nous afchions le rsultat dans le mme sous-processus que la lecture. Nous ralisons cela en groupant les commandes :
$ echo "Mon message" | (read VAR ; echo "VAR = $VAR") VAR = Mon message $

Toutefois, les shells peuvent comme cest le cas avec Ksh excuter la dernire commande dun pipeline lintrieur du processus shell pre. Dans ce cas, la variable VAR remplie est bien la mme que celle afche. Cette manipulation montre que la programmation sous shell nest pas toujours aussi simple que lon peut le croire et que seuls des tests approfondis permettent de sassurer de la portabilit dun script. Notons quune autre manire de procder, surtout utile pour stocker le rsultat dun long pipeline, consisterait diriger la sortie standard de la premire commande dans la variable dsire, avec loprateur $().
$ VAR=$(echo "Mon message") $ echo $VAR Mon message $

Il est souvent utile de dvier lentre standard dun processus pour lire le contenu dun chier. Cela permet videmment de traiter des donnes qui sont enregistres sur disque, mais peut aussi servir automatiser des programmes en mmorisant au pralable les lignes de commande saisir. Loprateur qui permet dalimenter lentre standard partir dun chier est < (suivi du nom du chier). La commande read peut servir lire une ligne complte dans un chier, en loccurrence un script crit plus haut :
$ read LIGNE < boucle_arg.sh $ echo $LIGNE #! /bin/sh $
Figure 44

Redirection read < chier

lments de programmation shell CHAPITRE 4

93

Symtriquement, on peut diriger la sortie standard dun processus an quelle aboutisse dans un chier plutt que sur le terminal. On emploie loprateur > suivi du nom du chier remplir. Un chier qui existait avant le lancement de la commande est cras, mais on peut utiliser dans ce cas loprateur >> qui ajoute les informations la n du chier en prservant son contenu prcdent.
$ echo === Dbut === > ls.txt $ cat ls.txt === Dbut === $ ls /etc/passw* >> ls.txt $ cat ls.txt === Dbut === /etc/passwd /etc/passwd/etc/passwd.bak $ echo === Fin === >> ls.txt $ cat ls.txt === Dbut === /etc/passwd /etc/passwd/etc/passwd.bak === Fin === $
Figure 45

Redirection echo > chier

Les utilitaires Unix fournissent souvent des informations de diagnostic plus ou moins importantes sur leur sortie derreur. Par dfaut, elles sont envoyes sur lcran au mme titre que les donnes crites sur la sortie standard, mais il est parfois indispensable de les sparer. Pour manipuler la sortie derreur standard, nous crons un petit script qui utilise la redirection >&2 que nous verrons plus bas pour envoyer les arguments de sa ligne de

94

Shells Linux et Unix par la pratique

commande vers la sortie derreur. Il agira donc comme la commande echo, mais en envoyant les informations sur la sortie derreur au lieu de la sortie standard.
echo_erreur.sh: #! /bin/sh echo "$@" >&2 # >&2 sera dcrit plus bas

Les oprateurs utiliss pour rediriger la sortie derreur sont 2> et 2>>, qui agissent similairement > et >>.
$ Ce $ Ce $ $ $ Ce $ ./echo_stderr Ce message message va sur la sortie ./echo_stderr Ce message message va sur la sortie cat fichier ./echo_stderr Ce message cat fichier message va sur la sortie va sur la sortie erreur erreur va sur la sortie erreur > fichier erreur va sur la sortie erreur 2> fichier erreur

Ces oprateurs sont trs utiles lorsque lon souhaite ne conserver que les rsultats dune commande, sans tenir compte des erreurs. Par exemple, lorsquon cherche un chier sur le disque laide de la commande find, il arrive quun grand nombre de rpertoires nen autorisent pas le parcours et lafchage est alors fortement perturb par de nombreux messages derreur :
$ find / -name passwd find: /var/tmp: Permission non accorde find: /var/lib/nfs/sm: Permission non accorde find: /var/lib/nfs/sm.bak: Permission non accorde find: /var/lib/slocate: Permission non accorde find: /var/log/squid: Permission non accorde find: /var/run/news: Permission non accorde find: /var/spool/at: Permission non accorde find: /var/spool/cron: Permission non accorde find: /var/spool/news/.xauth: Permission non accorde find: /var/spool/squid: Permission non accorde find: /var/gdm: Permission non accorde find: /proc/6/fd: Permission non accorde /etc/passwd find: /etc/X11/xdm/authdir: Permission non accorde find: /etc/default: Permission non accorde /etc/pam.d/passwd find: /tmp/orbit-da5id: Permission non accorde find: /proc/1/fd: Permission non accorde

lments de programmation shell CHAPITRE 4

95

find: /proc/2/fd: Permission non accorde find: /proc/3/fd: Permission non accorde find: /proc/4/fd: Permission non accorde [...] $

On peut alors renvoyer la sortie derreur vers /dev/null qui est un pseudo-priphrique spcial, sorte de trou noir capable dingurgiter aveuglment tout ce quon lui envoie. Il ne reste plus lcran que la sortie standard qui afche les rsultats utiles :
$ find / -name passwd 2>/dev/null /etc/passwd /etc/pam.d/passwd /usr/bin/passwd $

On utilise souvent une construction du mme genre avec la commande grep, pour rechercher une chane de caractres dans tous les chiers dun rpertoire, car elle envoie un message derreur pour chaque sous-rpertoire rencontr :
$ cd /etc $ grep snmp * grep: CORBA: est un rpertoire grep: X11: est un rpertoire grep: amd.conf: Permission non accorde grep: amd.net: Permission non accorde grep: at.deny: Permission non accorde grep: charsets: est un rpertoire grep: codepages: est un rpertoire grep: conf.linuxconf: Permission non accorde grep: cron.d: est un rpertoire grep: cron.daily: est un rpertoire rpc:snmp 100122 na.snmp snmp-cmc snmp-synoptics snmp-unisys grep: cron.hourly: est un rpertoire grep: cron.monthly: est un rpertoire grep: cron.weekly: est un rpertoire [...] $ grep snmp * 2>/dev/null rpc:snmp 100122 na.snmp snmp-cmc snmp-synoptics snmp-unisys services:snmp 161/udp # Simple Net Mgmt Proto services:snmp-trap 162/udp snmptrap # Traps for SNMP $

96

Shells Linux et Unix par la pratique

Parfois, on souhaite au contraire regrouper les deux sorties an de les envoyer dans un unique chier, ou de les faire transiter toutes les deux dans un tube. On notera en effet que dans un pipeline, seule la sortie standard dun processus est envoye via le tube au processus suivant. La sortie derreur continue dans ce cas tre dirige vers lcran :
$ grep snmp /etc/* | cat > fichier grep: /etc/CORBA: est un rpertoire grep: /etc/X11: est un rpertoire grep: /etc/amd.conf: Permission non accorde grep: /etc/amd.net: Permission non accorde grep: /etc/at.deny: Permission non accorde grep: /etc/charsets: est un rpertoire grep: /etc/codepages: est un rpertoire grep: /etc/conf.linuxconf: Permission non accorde grep: /etc/cron.d: est un rpertoire grep: /etc/cron.daily: est un rpertoire rpc:snmp 100122 na.snmp snmp-cmc snmp-synoptics snmp-unisys grep: /etc/cron.hourly: est un rpertoire grep: /etc/cron.monthly: est un rpertoire grep: /etc/cron.weekly: est un rpertoire [...] $ cat fichier /etc/rpc:snmp 100122 na.snmp snmp-cmc snmp-synoptics snmp-unisys 161/udp # Simple Net Mgmt Proto /etc/services:snmp $

/etc/services:snmp-trap 162/udp snmptrap # Traps for SNMP

Pour regrouper les deux sorties, on emploie lopration 2>&1, qui demande au shell dappliquer au descripteur de chier numro 2 (sortie derreur) la mme redirection que celle qui est applique au descripteur 1 (sortie standard). Les lecteurs familiariss avec la programmation systme en C reconnatront ici un appel systme dup2(1,2), qui duplique la conguration du descripteur de chier numro 1, referme ventuellement le descripteur 2 et le remplace par la copie du 1. Il faut bien comprendre que lopration 2>&1 ne fusionne pas les deux sorties mais recopie simplement la conguration de la sortie standard sur celle derreur. Si on applique une modication ultrieure au descripteur numro 1, avec une redirection > par exemple, la sortie derreur ne sera pas affecte. En rgle gnrale, on placera lopration 2>&1 en dernier, aprs toutes les redirections ventuelles. Voici un petit script qui envoie une ligne de message sur chacune de ses sorties.

lments de programmation shell CHAPITRE 4

97

deux_sorties.sh: 1 2 3 #! /bin/sh echo Cette ligne va sur la sortie standard ./echo_stderr Ce message est envoy sur la sortie d\'erreur

On remarquera au passage que lapostrophe contenue dans le deuxime message est protge par un backslash pour viter que le shell ne linterprte. Dans les exemples prsents ciaprs, on va observer que la fusion des deux sorties doit tre applique aprs une ventuelle redirection de la premire pour que lopration fonctionne comme on le souhaite :
$ ./deux_sorties.sh Cette ligne va sur la sortie standard Ce message est envoy sur la sortie d'erreur $ ./deux_sorties.sh 2>&1 > fichier Ce message est envoy sur la sortie d'erreur $ cat fichier Cette ligne va sur la sortie standard $ ./deux_sorties.sh > fichier 2>&1 $ cat fichier Cette ligne va sur la sortie standard Ce message est envoy sur la sortie d'erreur $

Dans un pipeline, en revanche, les drivations des sorties standards vers les tubes sont tablies avant que le shell ninterprte les diffrentes commandes et leurs ventuelles redirections. En consquence, lopration 2>&1 conserve bien sa signication. Nous pouvons vrier que le pipeline ne redirige par dfaut que la sortie standard, mais que la fusion opre bien comme nous le dsirons :

Figure 46

Pipeline deux_sorties 2>&1 | cat > chier

98

Shells Linux et Unix par la pratique

On notera bien que la sortie derreur de la seconde commande nest pas affecte par le regroupement opr dans la premire. On peut en avoir le cur net en remplaant la commande cat par un second appel deux_sorties (qui ignorera les donnes reues sur son entre standard) dans lequel on verra que les sorties sont indpendantes :
$ ./deux_sorties.sh 2>&1 | ./deux_sorties.sh > fichier Ce message est envoy sur la sortie d'erreur $ cat fichier Cette ligne va sur la sortie standard $

Pour agir globalement sur les sorties standards et derreur de tout le pipeline, on peut encadrer ce dernier entre parenthses :
$ (./deux_sorties.sh | cat ) > /dev/null Ce message est envoy sur la sortie d'erreur $ (./deux_sorties.sh | cat ) > /dev/null 2>&1 $

La plupart des applications qui utilisent la bibliothque C cest--dire la majorit des commandes et utilitaires Linux et Unix nemploient pas directement les descripteurs de chiers qui reprsentent des mcanismes dentres-sorties de bas niveau, mais prfrent les ux. Il sagit dune interface de niveau plus lev, qui gre automatiquement des mmoires tampons sur les chiers. Ainsi les critures rptes dans un chier sont-elles rendues plus efcaces en ne faisant intervenir les lments mcaniques, lents, du disque que lorsquun buffer est plein. Sauf conguration contraire, la sortie derreur nest pas dote de buffer, alors que la sortie standard en utilise un. Lorsque les deux sorties sont regroupes, il ne faut donc pas sattendre une synchronisation correcte entre les deux ux de donnes. Le type de buffer utilis pour la sortie standard est diffrent selon quelle est relie un terminal ou dirige vers un chier. Il sagit dun buffer fonctionnant ligne par ligne dans le premier cas, et par blocs bruts dans le second. Par exemple, lorsque nous excutons la commande :
$ grep snmp /etc/* > fichier 2>&1 $

La commande grep trouve peu de lignes contenant le mot snmp dans les chiers du rpertoire /etc. Ces rsultats restent donc dans le buffer jusqu la n de lexcution de la commande. Les messages derreur qui indiquent que lon a rencontr des sous-rpertoires

lments de programmation shell CHAPITRE 4

99

sont linverse crits immdiatement (la sortie derreur na pas de buffer). Le chier a donc lallure suivante :
grep: /etc/CORBA: est un rpertoire grep: /etc/X11: est un rpertoire grep: /etc/amd.conf: Permission non accorde grep: /etc/amd.net: Permission non accorde grep: /etc/at.deny: Permission non accorde grep: /etc/charsets: est un rpertoire [...] grep: /etc/wine: est un rpertoire grep: /etc/wvdial.conf: Permission non accorde Fichier binaire /etc/ld.so.cache concorde /etc/rpc:snmp 100122 na.snmp snmp-cmc snmp-synoptics snmp-unisys 161/udp # Simple Net Mgmt Proto /etc/services:snmp

/etc/services:snmp-trap 162/udp snmptrap # Traps for SNMP

En revanche, si nous recherchons toutes les lignes contenant le mot-cl root, la sortie standard est davantage sollicite. Le buffer va tre rempli plusieurs reprises, et la commande :
$ grep root /etc/* > fichier 2>&1 $

fournit un chier qui a une allure diffrente, o les critures des deux sorties sont entremles (les lignes de la sortie derreur sont prxes par grep) :
grep: /etc/CORBA: est un rpertoire grep: /etc/X11: est un rpertoire grep: /etc/amd.conf: Permission non accorde [...] grep: /etc/httpd: est un rpertoire grep: /etc/gtk: est un rpertoire /etc/aliases:postmaster: root /etc/aliases:bin: root /etc/aliases:daemon: root [...] /etc/inetd.conf:#time stream tcp nowait root /etc/inetd.conf:#time dgram udp wait root grep: /etc/ioctl.save: Permission non accorde grep: /etc/isdn: est un rpertoire

internal internal

100

Shells Linux et Unix par la pratique

[...] grep: /etc/vga: est un rpertoire grep: /etc/wine: est un rpertoire grep: /etc/wvdial.conf: Permission non accorde /etc/inetd.conf:telnet stream tcp nowait root /usr/sbin/tcpd in.telnetd /etc/inetd.conf:shell stream tcp nowait root /usr/sbin/tcpd in.rshd [...]

On retiendra donc quil nest gnralement pas possible de regrouper les sorties standards et derreur, et dobtenir un rsultat vraiment exploitable. Cest la raison pour laquelle lopration 2>&1 est surtout utilise pour rendre une commande silencieuse, en envoyant les sorties vers /dev/null. Enn, on notera quil est possible dabrger une redirection commune des deux sorties, du type >fichier2>&1, en une seule opration &>fichier, mais que cette mthode est assez rarement utilise.

Redirections avances
Comme les oprateurs 2> et 2>> ont pu le laisser deviner plus haut, les redirections offertes par le shell peuvent tre bien plus riches que les simples dviations des entres ou sorties standards. En ralit, les oprateurs de redirection se gnralisent dans les formes suivantes : n<, n>, et n>>, dans lesquelles n reprsente un numro de descripteur de chier (par dfaut, 0 pour <, et 1 pour > et >>). Loprateur de duplication peut aussi tre appliqu nimporte quel descripteur, et pas uniquement aux sorties standards et derreur. Ainsi la notation n>&m, o n et m sont deux numros de descripteurs, permet-elle de dupliquer m et den affecter une copie n, aprs avoir ferm ce dernier si besoin est. Nous comprenons alors le sens de la redirection >&2 utilise dans le script echo_stderr plus haut. Celle-ci permet denvoyer sur la sortie derreur ce que la commande echo envoie habituellement sur sa sortie standard. Ce principe est trs utilis dans la gestion derreur des scripts shell, pour sassurer que les messages derreur sont envoys sur la bonne sortie, conformment aux usages Unix courants.
if [ -a "$FICHIER" ] ; then echo "Le fichier $FICHIER nexiste pas !" >&2 exit 1 fi

Nous pouvons ouvrir un descripteur en lecture et en criture, grce loprateur n<>, ou fermer avec n>&- un descripteur an quil ne soit plus accessible. Ces symboles sont trs rarement utiliss dans les scripts shell.

lments de programmation shell CHAPITRE 4

101

Une dernire redirection est trs utile pour construire des scripts qui permettent dautomatiser des commandes complexes. Il sagit de loprateur << introduisant ce que lon nomme habituellement un document en ligne ou here document . Cet oprateur doit tre suivi dun mot qui reprsente une tiquette. Lentre standard de la commande est alors alimente avec le texte qui se trouve la suite, jusqu ce que lon rencontre une ligne qui ne comprenne que ltiquette mentionne plus haut. Le schma gnral est le suivant :
commande << ETIQUETTE Ici se trouve le texte qui sera envoy sur lentre standard de la commande. LETIQUETTE peut y apparatre, tant quelle nest pas seule sur une ligne. Le message se finit ici. ETIQUETTE

Ltiquette peut prendre la forme dun mot (souvent FIN, END ou EOF) ou de caractres que lon nutilisera pas dans le message (*** par exemple). Si on utilise loprateur <<- la place de <<, toutes les tabulations en dbut de ligne sont supprimes, y compris celles qui se situent devant ltiquette nale. Cela est trs pratique pour indenter le texte dune manire plus lisible, par exemple :
if [ -e fichier ] ; then commande <<- ETIQUETTE Ceci est le texte, qui se trouve ainsi indent de manire lisible et claire. ETIQUETTE fi

Lutilisation de ce type de redirection est souvent associe lmission de courrier ou la programmation diffre de commande interactive. Nous allons crire un script assez intressant, qui permet dautomatiser les transferts FTP. Pour cela, nous appellerons lutilitaire ftp et nous redirigerons son entre standard vers un document en ligne qui contient les commandes employer (open, cd, get, quit, etc.). Le premier problme que nous rencontrons est lauthentication de lutilisateur. Lorsquune connexion FTP est tablie, lapplication nous demande un nom dutilisateur et un mot de passe. La difcult rside en ceci que lutilitaire ftp naccepte pas de redirection dentre pour la saisie de ces informations. Pour automatiser son fonctionnement, il autorise nanmoins lexistence dun chier spcial, ~/.netrc, dans lequel on peut stocker lidentiant et le mot de passe pour chaque machine laquelle on est habitu se connecter. Dans de nombreux cas, lidentiant sera anonymous et le mot de passe sera reprsent par ladresse e-mail de lutilisateur. Ce chier ~/.netrc ne doit pas tre lisible par dautres personnes. Dans un second temps, nous allons tablir la connexion effective sur le site distant, et transfrer les chiers dsirs. Pour cela, nous utiliserons les commandes FTP suivantes : open suivi du nom de la machine, qui tablit la connexion en recherchant les paramtres dans ~/.netrc.

102

Shells Linux et Unix par la pratique

bin qui indique que le transfert correspond des chiers binaires. Cela nest vritablement utile que dans de rares cas, pour viter les interprtations errones des caractres \n et \r si on contacte un serveur sous Dos. Mme si cette commande nest en principe pas utile, je lemploie systmatiquement car il mest arriv de rechercher tout un aprsmidi un bogue inexistant dans un logiciel qui essayait dinterprter un chier binaire malencontreusement endommag par un client FTP sous Windows. prompt qui indique ftp de ne pas demander de conrmation chier par chier lors de transferts multiples. cd suivi du chemin daccs o se trouvent les chiers. mget suivi du nom des chiers rcuprer. Ce nom peut en effet inclure des caractres gnriques (comme *) pour transfrer plusieurs chiers. Le chier ~/.netrc doit contenir les lignes suivantes : machine suivi du nom de la station contacter. login suivi du nom de lutilisateur. Souvent, on utilisera le compte anonymous pour les transferts depuis les serveurs publics sur Internet. password suivi du mot de passe. Lorsque le transfert emploie le compte anonymous, on remplit par convention le mot de passe avec ladresse e-mail du demandeur, ce qui permet aux administrateurs dtablir des statistiques dutilisation de leur serveur. Notre programme attend les arguments suivants sur sa ligne de commande : machine contacter ; chemin daccs sur lhte distant ; chiers transfrer ; nom de connexion (facultatif, par dfaut anonymous) ; mot de passe (facultatif, par dfaut adresse e-mail).
ftpauto.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #! /bin/sh # Paramtrage du transfert dsir MACHINE=${1:?Pas de machine indique} CHEMIN=${2:?Pas de chemin indiqu} FICHIERS=${3:?Pas de fichiers indiqus} LOGIN=${4:-anonymous} PASSWORD=${5:-$USER@$HOSTNAME} # D'abord sauver l'ventuel fichier ~/.netrc if [ -f ~/.netrc ] ; then mv ~/.netrc ~/.netrc.back

lments de programmation shell CHAPITRE 4

103

16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

fi # Crer un nouveau ~/.netrc avec les infos concernant # uniquement la connexion voulue ANCIEN_UMASK=$(umask) umask 0177 echo machine $MACHINE > ~/.netrc echo login $LOGIN >> ~/.netrc echo password $PASSWORD >> ~/.netrc umask $ANCIEN_UMASK # Lancer la connexion ftp <<- FIN open $MACHINE bin prompt cd $CHEMIN mget $FICHIERS quit FIN # Effacer .netrc et rcuprer l'ancien rm -f ~/.netrc if [ -f ~/.netrc.back ] ; then mv ~/.netrc.back ~/.netrc fi

Dans les premires versions de ce script, je commenais par crer le chier .netrc puis je modiais ses permissions daccs grce la commande chmod. Il sagissait dune faille de scurit, puisquil existait un petit intervalle de temps pendant lequel le chier ntait pas correctement protg. Lutilisation de la commande umask, comme on le fait ici, permet de garantir la condentialit de ce chier ds sa cration. Nous pouvons vrier son fonctionnement :
$ ls man* ls: man*: Aucun fichier ou rpertoire de ce type $ ./ftpauto.sh ftp.lip6.fr pub/linux/french/docs man-fr* Interactive mode off. $ ls man* man-fr-0.9.tar.gz $

Ce petit script peut tre trs utile pour programmer des rapatriements lorsque le rseau est moins charg, ou pour automatiser des transferts priodiques de donnes (sauvegardes par exemple).

104

Shells Linux et Unix par la pratique

On peut utiliser avantageusement les documents en ligne pour automatiser la plupart des commandes interactives. Par exemple jai rencontr des programmeurs qui automatisaient ainsi le formatage de partitions de mmoire ash avec loutil fdisk pour produire en srie des installations de Linux sur systmes embarqus. Une autre utilisation courante, qui vous sera propose en exercice, consiste piloter avec un script shell lutilitaire mysql permettant dinterroger une base de donnes en lui transmettant des requtes SQL.

Structures de contrle
En algorithmique, lexcution des instructions se dnit classiquement selon trois types denchanements : Squence : les instructions sont excutes lune aprs lautre, chacune attendant la n de la prcdente pour dmarrer. Slection : certaines instructions seront excutes ou non en fonction dune condition. Itration : une instruction est rpte plusieurs fois, en fonction dune condition. Nous pouvons ajouter aux possibilits des scripts shell les excutions parallles de tches, que nous avons pu rencontrer dans les commandes composes. Les excutions en squence ont dj t tudies avec les commandes composes et les pipelines. Nous allons donc analyser les deux mcanismes de slection, puis ceux ditration.

Slection dinstructions
Construction if-then-else

Nous avons dj rencontr la structure if-then-else, qui permet dexcuter des instructions sous certaines conditions. Voici la syntaxe complte de cette construction :
if condition_1 then commande_1 elif condition_2 then commande_2 else commande_n fi

La condition_1 est excute. Il peut sagir dune commande compose quelconque, mais on emploie souvent la commande [] pour vrier une condition. Nous la dtaillerons plus avant. Le code de retour de condition_1 est examin. Sil est nul, alors la condition est considre comme vraie. Dans ce cas, la commande compose commande_1 est excute, puis le contrle est transfr la n de la construction, aprs le fi.

lments de programmation shell CHAPITRE 4

105

Si le code de retour de condition_1 est non nul, elle nest pas vrie. Le contrle passe alors la deuxime partie de la slection, linstruction elif (contraction de elseif, sinon si). La condition_2 est excute puis, si elle est vrie, la commande_2. Sinon, le contrle passe la dernire partie, indique par else, et la commande_n est excute. Il faut ncessairement sparer la condition indique aprs un if du mot-cl then. Pour cela, on insre gnralement un retour la ligne comme nous lavons fait ci-dessus, mais on peut galement utiliser un point-virgule cet effet :
if condition ; then commande fi

Conditions et tests

Dans une construction if-then, la condition qui se trouve aprs le if ou le elif est reprsente par une fonction dont le code de retour correspond une valeur vraie (0) ou fausse (retour non nul). Dans de trs nombreux cas, les conditions que nous souhaiterons vrier consisteront en des comparaisons numriques, comparaisons de chanes de caractres, ou consultations de ltat dun chier. Pour ce faire, le shell nous offre un oprateur interne nomm test qui accepte les arguments suivants en ligne de commande :
Option Vraie si
Le chier indiqu existe. Loption a nest pas dnie dans Single Unix version 3 et on lui prfrera e. Le chier indiqu est un nud spcial qui dcrit un priphrique en mode bloc. Le chier indiqu est un nud spcial qui dcrit un priphrique en mode caractre. Le rpertoire indiqu existe. Le chier indiqu est un chier rgulier. Le bit Set-GID du chier indiqu est positionn. Le chier indiqu est un lien symbolique. Le chier indiqu appartient au mme groupe que le GID effectif du processus invoquant la commande test. Le bit Sticky du chier indiqu est positionn. La longueur de la chane indique est non nulle. Le chier a t modi depuis son dernier accs en lecture. Le chier indiqu appartient au mme utilisateur que lUID effectif du processus invoquant la commande test. Le chier indiqu est un tube nomm (le FIFO). Le chier indiqu est lisible. La taille du chier indiqu est non nulle. Le chier indiqu est une socket.

-a fichier -e fichier -b fichier -c fichier -d repertoire -f fichier -g fichier -h fichier -L fichier -G fichier -k fichier -n chaine -N fichier -O fichier -p fichier -r fichier -s fichier -S fichier

106

Shells Linux et Unix par la pratique

Option

Vraie si
Le descripteur de chier correspond un terminal. Le bit Set-UID du chier indiqu est positionn. On peut crire dans le chier indiqu. Le chier indiqu est excutable. La longueur de la chane indique est nulle. La chane est non nulle. Les deux chanes sont identiques. Les deux chanes sont diffrentes. La premire chane apparat avant la seconde, dans un tri lexicographique croissant. La premire chane apparat aprs la seconde, dans un tri lexicographique croissant. Les deux valeurs arithmtiques sont gales. La premire valeur est suprieure ou gale la seconde. La premire valeur est strictement suprieure la seconde. La premire valeur est infrieure ou gale la seconde. La premire valeur est strictement infrieure la seconde. Les deux valeurs arithmtiques sont diffrentes. Le fichier_1 est le mme que le fichier_2. Il peut sagir de deux noms (liens physiques) diffrents dans le systme de chiers, correspondant au mme contenu sous-jacent. La comparaison concerne le numro de priphrique de support et le numro di-nud. La date de dernire modication du fichier_1 est plus rcente que celle du fichier_2. La date de dernire modication du fichier_1 est plus ancienne que celle du fichier_2.

-t descripteur -u fichier -w fichier -x fichier -z chaine chaine chaine_1 = chaine_2 chaine_1 != chaine_2 chaine_1 < chaine_2 chaine_1 > chaine_2 valeur_1 -eq valeur_2 valeur_1 -ge valeur_2 valeur_1 -gt valeur_2 valeur_1 -le valeur_2 valeur_1 -lt valeur_2 valeur_1 -ne valeur_2 fichier_1 -ef fichier_2

fichier_1 -nt fichier_2 fichier_1 -ot fichier_2

Les mmes options peuvent tre utilises dans la commande interne [] qui est un synonyme de test. Si on indique un nom de chier correspondant un lien symbolique, seules les options
-h et -L soccuperont du lien proprement dit ; les autres options traiteront la cible vise

par le lien. Le script suivant tudie les chiers indiqus en argument, et prsente leurs caractristiques.
test_fichier.sh: 1 2 3 4 5 6 7 8 9 10 11 12 #! /bin/sh for i in echo if [ if [ "$@" ; do "$i : " -L "$i" ] ; then echo " (lien symbolique) " ; fi -e "$i" ] ; then echo -n " type = " if [ -b "$i" ] ; then echo "spcial bloc " ; fi if [ -c "$i" ] ; then echo "spcial caractre " ; fi if [ -d "$i" ] ; then echo "rpertoire " ; fi if [ -f "$i" ] ; then echo "fichier rgulier " ; fi if [ -p "$i" ] ; then echo "tube nomm " ; fi

lments de programmation shell CHAPITRE 4

107

13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29

if [ echo if [ if [ if [ echo echo if [ if [ if [ echo if [ if [ else fi done

-S -n -g -u -k "" -n -r -w -x "" -G -O

"$i" ] ; then " bits = " "$i" ] ; then "$i" ] ; then "$i" ] ; then " accs "$i" ] ; "$i" ] ; "$i" ] ;

echo "socket " ; fi echo -n "Set-GID " ; fi echo -n "Set-UID " ; fi echo -n "Sticky " ; fi

= " then echo -n "lecture " ; fi then echo -n "criture " ; fi then echo -n "excution " ; fi

"$i" ] ; then echo " appartient notre GID" ; fi "$i" ] ; then echo " appartient notre UID" ; fi

echo " n'existe pas"

Dans le script, chaque occurrence de $i est encadre par des guillemets, an que les chiers dont le nom contient une espace puissent tre traits correctement. Essayez de supprimer les guillemets et examinez le comportement du script.
$ ./test_fichier.sh test_fichier.sh test_fichier.sh : type = fichier rgulier bits = accs = lecture criture excution appartient notre GID appartient notre UID $ ./test_fichier.sh /dev /dev : type = rpertoire bits = accs = lecture excution $ ./test_fichier.sh /dev/tty /dev/tty : type = spcial caractre bits = accs = lecture criture $ ./test_fichier.sh "/mnt/dos/Mes documents" /mnt/dos/Mes documents : type = rpertoire bits = accs = lecture criture excution appartient notre GID appartient notre UID $

108

Shells Linux et Unix par la pratique

Loption -t permet de vrier si un descripteur correspond bien un terminal, comme le montre lexemple suivant :
$ if [ -t 0 ] ; then echo Oui ; else echo Non ; fi Oui $ if [ -t 3 ] ; then echo Oui ; else echo Non ; fi Non $ (if [ -t 3 ] ; then echo Oui ; else echo Non ; fi ) 3>&2 Oui $

En ce qui concerne les comparaisons de chanes, il est recommand de toujours encadrer le nom des variables par des guillemets, an dviter les erreurs, susceptibles de se produire si une chane est vide :
$ VAR1=abcd $ VAR2= $ if [ $VAR1 = $VAR2 ] ; then echo Oui ; else echo Non ; fi [: abcd: unary operator expected Non $ if [ "$VAR1" = "$VAR2" ] ; then echo Oui ; else echo Non ; fi Non $ VAR2=abcd $ if [ "$VAR1" = "$VAR2" ] ; then echo Oui ; else echo Non ; fi Oui $

On peut associer plusieurs conditions avec les oprateurs suivants :


Option Vraie si
La condition est fausse. Les deux conditions sont vraies. Lune au moins des conditions est vraie.

! condition
condition_1 a condition_2 condition_1 o condition_2

Ces options rendant les expressions difcilement lisibles, il est prfrable de juxtaposer directement plusieurs commandes de test laide des symboles && || et !, dj rencontrs :

lments de programmation shell CHAPITRE 4

109

$ min=15 $ max=23 $ val=19 $ if [ $val -lt $min ] || [ $val -gt $max ]; then echo Alarme; fi $ val=14 $ if [ $val -lt $min ] || [ $val -gt $max ]; then echo Alarme; fi Alarme $ val=25 $ if [ $val -lt $min ] || [ $val -gt $max ]; then echo Alarme; fi Alarme $

Construction case-esac

La seconde structure de slection propose par le shell est introduite par le mot-cl case, et termine par esac. La forme gnrale en est la suivante :
case expression in motif_1 ) commande_1 ;; motif_2 ) commande_2 ;; ... esac

Lexpression indique la suite du case est value puis son rsultat est compar (en tant que chane de caractres) avec les diffrents motifs fournis ensuite. Si la correspondance entre lexpression et un motif est ralise, les commandes composes qui suivent le motif sont excutes, puis le contrle passe la n de la structure, aprs le esac. Les motifs peuvent contenir des caractres gnriques du shell, comme lastrisque qui remplace nimporte quelle squence de caractres, le point dinterrogation qui remplace un unique caractre, ou un encadrement de caractres entre crochets indiquant une alternative ou un intervalle. Des motifs peuvent galement tre juxtaposs avec un symbole | signiant OU. Lexemple suivant que lon peut considrer comme une portion de script dinstallation dune application sous Linux va invoquer lutilitaire uname pour connatre le numro de version du noyau en cours dutilisation. En fonction du rsultat, il sarrtera ou poursuivra lexcution, ventuellement aprs avoir interrog lutilisateur. An de pouvoir tricher et tester les diffrents cas, on ninvoque lutilitaire uname que si aucun argument na t indiqu sur la ligne de commande, sinon on emploie le premier transmis en guise de numro de version.
test_noyau.sh: 1 2 3 #! /bin/sh i=$(uname -r)

110

Shells Linux et Unix par la pratique

4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

i=${1:-$i} case "$i" in 2.4.* | 2.5.* ) Type_noyau="2.4" ;; 2.2.* | 2.3.* ) Type_noyau="2.2" ;; 2.[01].* ) Type_noyau="2.0" ;; 1.* | 0.* ) echo "Trop ancien, impossible de continuer" exit 1 ;; * ) Type_noyau="Inconnu" echo "Noyau inconnu ; continuer l'installation ?" read Reponse case "$Reponse" in O* | o* | Y* | y* ) echo Ok ;; * ) exit 1 ;; esac ;; esac echo "Installation pour noyau de type $Type_noyau"

On peut remarquer que limbrication de deux structures case-esac ne pose pas de problme.
$ uname -r 2.2.12-20 $ ./test_noyau.sh Installation pour noyau de type 2.2 $ ./test_noyau.sh 1.2.12 Trop ancien, impossible de continuer $ ./test_noyau.sh 2.3.29 Installation pour noyau de type 2.2 $ ./test_noyau.sh 2.4.1 Installation pour noyau de type 2.4 $ ./test_noyau.sh 3.0 Noyau inconnu ; continuer l'installation ? o Ok Installation pour noyau de type Inconnu $ ./test_noyau.sh 3.1 Noyau inconnu ; continuer l'installation ? non $

lments de programmation shell CHAPITRE 4

111

Comme la slection case accepte la mise en correspondance de motifs sous forme de chanes comportant des caractres gnriques, cette construction permet des applications bien plus puissantes que ses homologues dans les langages de programmation plus classiques, qui nacceptent que des arguments numriques ou des chanes xes. Il est possible dutiliser case pour dnir une fonction qui vrie si une chane de caractres peut correspondre un motif contenant des caractres gnriques. Les dnitions de fonctions sont dtailles plus bas dans ce chapitre. La fonction correspondance() suivante renvoie une valeur vraie (0) ou fausse (1) suivant que son premier argument $1 peut tre mis en correspondance avec le second $2.
function correspondance { case "$1" in $2) return 0 ;; *) return 1 ;; esac }

Nous pouvons en vrier le fonctionnement en dnissant directement la fonction en ligne de commande :


$ function correspondance { > case "$1" in > > > } $ if correspondance "azer" "azer"; then echo Oui; else echo Non; fi Oui $ if correspondance "azer" "azur"; then echo Oui; else echo Non; fi Non $ if correspondance "azer" "az?r"; then echo Oui; else echo Non; fi Oui $ if correspondance "azer" "a*r"; then echo Oui; else echo Non; fi Oui $ if correspondance "azer" "a*b"; then echo Oui; else echo Non; fi Non $ $2) return 0;; *) return 1;;

> esac

Ce mcanisme peut tre utile lorsquon dsire largir le champ daction dun script en le rendant plus gnral.

112

Shells Linux et Unix par la pratique

Itrations dinstructions
Les squences dinstructions se prsentent traditionnellement sous deux formes : celles qui consistent en un nombre limit de boucles (rpter 10 fois la squence) et celles qui dpendent dune condition darrt (recommencer tant que le maximum nest pas atteint). Les deux constructions proposes par le shell retent ces deux mcanismes.
Rptitions while-do et until-do

Les deux structures while-do-done et until-do-done servent rpter une squence dinstructions jusqu ce quune condition soit vrie. Elles se prsentent ainsi :
while condition do commandes done

et
until condition do commandes done

La premire construction rpte les commandes composes qui se trouvent dans le corps de la boucle tant que (while) la condition est vrie. La seconde les rpte jusqu ce que (until) la condition devienne vraie ; en dautres termes, elle les rpte tant que la condition est fausse. Le code de retour gnral est celui de la dernire commande excute. Ces structures permettent dmuler le fonctionnement de la boucle for telle quelle se prsente dans les langages comme le C, en assurant un comptage. Par exemple, le script suivant afche la factorielle dun entier pass en argument (on pourrait rduire le programme en utilisant une variable de moins, mais la lisibilit serait amoindrie).
factorielle.sh: 1 2 3 4 5 6 7 8 9 10 11 #! /bin/sh n=${1:-1} i=1 f=1 while [ $i -le $n ] ; do f=$((f * i)) i=$((i + 1)) done echo "$n! = $f"

lments de programmation shell CHAPITRE 4

113

Le script gre correctement le calcul 0!, en revanche, le calcul nest exact que jusqu 20! environ (cela dpend du shell) car, au-del, le rsultat dpasse les limites de larithmtique entire :
$ ./factorielle.sh 0 0! = 1 $ ./factorielle.sh 1 1! = 1 $ ./factorielle.sh 4 4! = 24 $ ./factorielle.sh 5 5! = 120 $ ./factorielle.sh 10 10! = 3628800 $ ./factorielle.sh 20 20! = 2432902008176640000 $ ./factorielle.sh 21 21! = -4249290049419214848

Rupture de squence avec break et continue

Lorsquon utilise une construction de rptition while ou until, il arrive frquemment que, dans le corps mme de la boucle, des instructions inuent sur la condition utilise pour limiter les itrations. Les ruptures de squences qui se produisent dans ces boucles sont introduites par les mots-cls break et continue. Le premier sert sortir immdiatement de la boucle en cours, en transfrant le contrle aprs le done associ au while-do ou until-do. Certains programmeurs prfrent crire autant que possible leurs algorithmes sous forme de boucles innies dont ils demandent explicitement la sortie grce break. La lisibilit est ainsi quelque peu amliore dans le cas o plusieurs voies de sortie distinctes seraient rencontres. En voici un exemple :
interprete_chaine.sh 1 2 3 4 5 6 7 8 9 10 11 #! /bin/sh while true ; do echo -n "[Commande]>" if ! read chaine ; then echo "Saisie invalide" break fi if [ -z "$chaine" ] ; then echo "Saisie vide" break

114

Shells Linux et Unix par la pratique

12 13 14 15 16 17 18 19

fi if [ "$chaine" = "fin" ] ; then echo "Fin demande" break fi eval $chaine done echo "Au revoir"

Ce mini-interprteur offre trois sorties possibles :


$ ./interprete_chaine.sh [Commande]>ls descente_repertoires.sh test_fichier.sh deux_sorties.sh test_noyau.sh echo_stderr.sh ftpauto.sh test_select.sh interprete_chaine.sh factorielle.sh [Commande]> (Entre) Saisie vide Au revoir $ ./interprete_chaine.sh [Commande]> (Contrle-D) Saisie invalide Au revoir $ ./interprete_chaine.sh [Commande]>ls /dev | wc 2395 2395 14963 [Commande]>fin Fin demande Au revoir $

La commande true renvoie toujours vrai, linverse de son acolyte false. En dehors de while true; do et de until false; do, on les emploie galement lors de la mise au point dun script pour remplacer temporairement une commande et vrier ainsi ce qui se passe lorsquelle choue ou russit. Le mot-cl break du shell peut galement tre suivi dun entier indiquant le nombre de boucles imbriques dont on dsire sortir en une seule fois. Par dfaut, la valeur est 1, mais il est ainsi possible de transfrer le contrle la n dune boucle extrieure plus large. On notera que les scripts qui font usage de cette possibilit sont souvent moins lisibles et plus difciles maintenir. Le mot-cl continue permet de renvoyer le contrle au dbut de la boucle. On revient ainsi immdiatement lemplacement du test while ou until sans excuter les instructions suivantes. Nous en verrons un exemple dans le script prsent plus bas dans le paragraphe qui traite des fonctions. Comme pour break, il est possible dajouter un argument entier qui indique combien de boucles imbriques doivent tre traverses pour revenir au dbut.

lments de programmation shell CHAPITRE 4

115

Construction for-do

La boucle for est un grand classique que lon rencontre dans la plupart des langages de programmation. Hlas, son fonctionnement dans les scripts shell est totalement diffrent de celui qui est habituellement mis en uvre dans les autres langages. Une boucle fordo-done avec le shell se prsente ainsi :
for variable in liste_de_mots do commandes done

La variable va prendre successivement comme valeur tous les mots de la liste suivant in et le corps de la boucle sera rpt pour chacune de ces valeurs. Voyons un exemple :
exemple_for_1.sh: 1 2 3 4 5 #! /bin/sh for i in 1 2 3 5 7 11 13 ; do echo "$i2 = $((i * i))" done

dont lexcution afche les carrs des premiers entiers indiqus :


$ ./exemple_for_1.sh 12 = 1 22 = 4 32 = 9 52 = 25 72 = 49 112 = 121 132 = 169 $

On utilise souvent for avec les listes "$@" et * qui reprsentent respectivement lensemble des paramtres de la ligne de commande et la liste des chiers prsents dans le rpertoire en cours. Par dfaut, for utilise la liste in"$@" si on omet ce mot-cl.
exemple_for_2.sh: 1 2 3 4 5 #! /bin/sh for i ; do echo $i done

116

Shells Linux et Unix par la pratique

Litration a bien lieu pour tous les paramtres en ligne de commande :


$ ./exemple_for_2.sh ab cd "ef gh" ab cd ef gh $

On utilise aussi assez souvent la boucle for dans une invocation sur une seule ligne, directement depuis le symbole dinvite du shell, pour traiter un ensemble de chiers en une seule fois :
$ ls data.tgz doc.tgz icones.tgz sons.tgz src.tgz $ for i in *.tgz ; do mv $i ${i%%.tgz}.tar.gz ; done $ ls data.tar.gz doc.tar.gz icones.tar.gz sons.tar.gz src.tar.gz $

Toutefois, pour ce genre dopration qui peut se rvler destructrice, il est prfrable de sassurer de la validit de la commande en lexcutant dabord blanc , prcde dun echo qui afchera les modications prvues :
$ mv mv mv mv mv $ for i in *.tgz ; do echo mv $i ${i%%.tgz}.tar.gz ; done data.tgz data.tar.gz doc.tgz doc.tar.gz icones.tgz icones.tar.gz sons.tgz sons.tar.gz src.tgz src.tar.gz

On remarquera encore que si certains chiers risquent de comporter un espace dans leur nom, il faut encadrer de guillemets les utilisations de $i et ${i%%.tgz}.
Choix et itration avec select-do

La construction select-do-done est une structure assez originale qui peut se rvler trs utile dans certains scripts. Elle se prsente sous laspect suivant :
select variable in liste_de_mots do commandes done

Le shell dveloppe et afche sur la sortie derreur la liste de mots qui est fournie, en les faisant prcder de numros dordre. Il afche ensuite le symbole dinvite reprsent par la variable interne PS3 et lit une ligne depuis son entre standard. Si elle contient un numro qui reprsente lun des mots afchs, ce dernier est plac dans la variable dont le nom est prcis aprs le select. Ensuite, les commandes sont excutes, puis le processus recommence.

lments de programmation shell CHAPITRE 4

117

La boucle sarrte lorsque la lecture rvle une n de chier (pression sur Contrle-D depuis le clavier par exemple), ou si un break est rencontr au cours des commandes. Lorsque la lecture renvoie une ligne vide, lensemble des mots est afch nouveau et la saisie reprend. Si la ligne lue ne correspond aucun mot afch, la variable reste vide, mais la saisie est transmise dans la variable spciale REPLY. Ce mcanisme permet de crer rapidement des menus simples dans des scripts, ou dtablir une slection de chiers traiter. Lexemple suivant est un peu complexe, mais il illustre plusieurs points intressants. Il sagit dun script qui afche la liste des chiers du rpertoire en cours et propose plusieurs actions sur le chier slectionn :
menu_fichier.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #! /bin/sh # Cette fonction reoit en argument le nom d'un fichier, et # propose les diffrentes actions possibles. function action_fichier () { local reponse local saisie echo "*********************************************" PS3=" Action sur $1 : " select reponse in Infos Copier Dplacer Dtruire Retour do case $reponse in Infos ) echo ls -l $1 echo ;; Copier ) echo -n "Copier $1 vers ? " if ! read saisie ; then continue ; fi cp $1 $saisie ;; Dplacer ) echo -n "Nouvel emplacement pour $1 ? " if ! read saisie ; then continue ; fi mv $1 $saisie break ;; Dtruire ) if rm -i $1 ; then break; fi ;; Retour ) break ;; * ) if [ "$REPLY" = "0" ] ; then break ; fi

118

Shells Linux et Unix par la pratique

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

echo "$REPLY n'est pas une rponse valide" echo ;; esac done } # Cette routine affiche la liste des fichiers prsents dans # le rpertoire, et invoque la fonction action_fichier si la # saisie est correcte. Elle se termine si on slectionne "0" function liste_fichiers () { echo "*********************************************" PS3="Fichier traiter : " select fichier in * do if [ ! -z "$fichier" ] ; then action_fichier $fichier return 0 fi if [ "$REPLY" = "0" ] ; then return 1 fi echo "==> Entrez 0 pour Quitter" echo done } # Exemple de boucle tant qu'une fonction russit. # Le deux-points dans la boucle signifie "ne rien faire" while liste_fichiers ; do : ; done

Pour commencer, la fonction action_fichier initialise la variable PS3 celle qui sera afche avant la saisie avec une chane comportant un retour la ligne ! Cela ne pose aucun problme et amliore linterface du logiciel. On constate ensuite quen fonction de lopration rclame, la commande correspondante (ls, cp, mv, rm) est invoque et que lon sort de la boucle si le chier a t dplac ou supprim. La fonction liste_fichiers afche tous les chiers et invoque la routine prcdente si la saisie est correcte. Attention, un return dans une construction select ne fait sortir que de cette boucle et pas de la fonction complte. Dans les deux cas, lexcution continue aprs le done. Ici, il ny a aucune instruction, mais on peut y ajouter un echo pour en avoir le cur net. La boucle while ne contient quun test, qui vrie si le code de retour de liste_fichiers est bien vrai (nul). Le corps de la boucle ne contient aucune instruction utile, ce que lon reprsente par le caractre :.

lments de programmation shell CHAPITRE 4

119

Voici quelques exemples dexcution :


$ ./menu_fichier.sh ********************************************* 1) doc.tgz 3) menu_fichier.sh 5) src.tgz 2) icones.tgz 4) sons.tgz Fichier traiter : 2 ********************************************* 1) Infos 3) Dplacer 5) Retour 2) Copier 4) Dtruire Action sur icones.tgz : 2 Copier icones.tgz vers ? essai 1) Infos 3) Dplacer 5) Retour 2) Copier 4) Dtruire Action sur icones.tgz : 5 ********************************************* 1) doc.tgz 3) icones.tgz 5) sons.tgz 2) essai 4) menu_fichier.sh 6) src.tgz Fichier traiter : 2 ********************************************* 1) Infos 3) Dplacer 5) Retour 2) Copier 4) Dtruire Action sur essai : 4 Dtruire essai ? o ********************************************* 1) doc.tgz 3) menu_fichier.sh 5) src.tgz 2) icones.tgz 4) sons.tgz Fichier traiter : 5 ********************************************* 1) Infos 3) Dplacer 5) Retour 2) Copier 4) Dtruire Action sur src.tgz : 3 Nouvel emplacement pour src.tgz ? sources.tar.gz ********************************************* 1) doc.tgz 3) menu_fichier.sh 5) sources.tar.gz 2) icones.tgz 4) sons.tgz Fichier traiter : 0 $

120

Shells Linux et Unix par la pratique

Bien sr, la prsentation nest ni trs esthtique ni trs ergonomique, mais la structure select permet quand mme de simplier linterface de petits scripts en proposant lutilisateur un choix de commandes ou de chiers quil peut manipuler sans compliquer le dveloppement du programme. Cela peut tre trs utile, par exemple, dans des scripts dinstallation o lutilisateur choisit des modules ou des rpertoires copier.

Fonctions
Nous avons dj rencontr plusieurs reprises des fonctions, ne serait-ce que dans le dernier exemple. Nous allons examiner quelques points de dtail les concernant. Dune manire gnrale, une fonction se dnit ainsi :
function nom_de_la_fonction { commandes }

Toutefois, on peut supprimer le mot-cl function condition dajouter deux parenthses seules aprs le nom de la fonction (), soit les parenthses, mais pas les deux la fois. On peut donc crire au choix :
function ma_fonction { ... }

ou
ma_fonction () { ... }

Attention Le fait dcrire function et dajouter les parenthses nest pas portable, mais cela fonctionne sur de nombreux shells.

Jai souvent tendance conserver plutt function que les parenthses, considrant que cet indice marque bien le fait que lon est en train de dnir une fonction, mme aux yeux de quelquun qui nest pas familiaris avec la programmation shell. Ce nest toutefois quune question de choix personnel, et chacun est libre demployer lcriture de son

lments de programmation shell CHAPITRE 4

121

choix. Lorsque la portabilit du script est importante, on utilisera plutt la seconde forme, avec les parenthses, car cest celle prconise par les spcications Single Unix version 3. De mme, laccolade ouvrante peut tre place juste aprs les parenthses, mme si le positionnement en dbut de ligne pour une dnition de fonction est gnralement considr comme lcriture la plus lisible en programmation shell. Les arguments que lon transmet une fonction sont placs la suite de son nom lors de linvocation. Ils ne sont pas indiqus lors de la dnition de la fonction il nexiste pas de notion de prototype mais seront placs automatiquement dans les paramtres positionnels $1, $2$n, o on pourra les consulter dans le corps de la fonction. Une attitude dfensive dans la programmation de script imposerait que lon vrie au moins que le bon nombre darguments a bien t transmis. Cela est possible grce au paramtre spcial $# qui contient le nombre de paramtres positionnels reus. Par exemple :
fonction_1.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #! /bin/sh function trois_arg { # Cette routine attend trois arguments if [ $# -ne 3 ] ; then echo "Nombre d'arguments errons dans trois_arg()" return fi echo "Traitement des arguments de trois_arg ()" } trois_arg trois_arg un deux trois quatre trois_arg un deux trois

Lors de lexcution de ce script, les deux premires invocations sont bien rejetes :
$ ./fonction_1.sh Nombre d'arguments errons dans trois_arg() Nombre d'arguments errons dans trois_arg() Traitement des arguments de trois_arg () $

122

Shells Linux et Unix par la pratique

On peut aussi crer une fonction qui prend un nombre variable darguments :
fonction_2.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #! /bin/sh function additionne { # Cette routine additionne tous ses arguments, et # affiche le rsultat sur la sortie standard local somme local i somme=0 for i in "$@" ; do somme=$((somme + i)) done echo $somme } # Appeler la fonction avec les arguments reus # en ligne de commande. additionne "$@"

Nous pouvons directement effectuer les expriences depuis la ligne de commande, un argument non numrique tant considr comme nul :
$ ./fonction_2.sh 1 2 3 6 $ ./fonction_2.sh 1 2 trois 3 $ ./fonction_2.sh 1 2 3 4 5 6 7 8 9 10 55 $

Au sein dune fonction, le paramtre positionnel $0 conserve la mme valeur que dans le reste du script, cest--dire le nom et le chemin daccs du chier contenant le script. On peut donc lemployer pour afcher des messages derreur. Le mot-cl local permet dindiquer quune variable ne sera visible que dans la fonction o elle est dnie. Lintrt en est double : viter de modier involontairement une variable galement utilise lextrieur de la fonction. Cela est dautant plus vrai lorsquon utilise des variables aux noms courants, comme i, f, tmp, etc. Le risque est moins grand avec adresse_ip_serveur; toutefois, la dclaration de la variable avec local nous protgera contre tout risque dinterfrence avec le reste du script.

lments de programmation shell CHAPITRE 4

123

Lutilisation des variables locales rend possible lappel rcursif de la fonction. Le script prsent ci-aprs utilise ce mcanisme pour afcher larborescence des sousrpertoires partir dun point donn de la hirarchie de chiers. La dclaration locale de f est obligatoire car, autrement, lappel rcursif de la fonction aurait modi son contenu avant le retour.
descente_repertoires.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #! /bin/sh function explore_repertoire { local f local i # Faire prcder le nom du rpertoire reu en premier # argument par autant de caractres blancs que la # valeur du second argument. i=0 while [ $i -lt $2 ] ; do echo -n " " i=$((i + 1)) done echo "$1" # Se dplacer dans le 1er rpertoire. Si chec -> abandon if ! cd "$1" ; then return ; fi # Balayer tout le contenu du rpertoire for f in * ; do # Sauter les liens symboliques if [ -L "$f" ] ; then continue fi # Si on a trouv un sous-rpertoire, l'explorer en # incrmentant sa position (de 4 pour lesthtique) if [ -d "$f" ] ; then explore_repertoire "$f" $(($2 + 4)) fi done # Revenir dans le rpertoire initial cd .. } # Lancer l'exploration partir de l'argument explore_repertoire "$1" 0

124

Shells Linux et Unix par la pratique

On peut remarquer dans ce script lemploi de la commande continue pour passer llment suivant de litration en sautant la n de la boucle.
$ ./descente_repertoires.sh /dev/ /dev/ ida inet pts rd $ ./descente_repertoires.sh /usr/src/linux/arch/ /usr/src/linux/arch/ i386 boot compressed tools kernel lib math-emu mm $ ./descente_repertoires.sh /proc/sys/net/ /proc/sys/net/ 802 core ethernet ipv4 conf all default eth0 lo neigh default eth0 lo route token-ring unix $

Quand une fonction se termine, elle renvoie un code de retour qui correspond celui de la dernire instruction excute. Linstruction return permet, si on le dsire, de prciser explicitement un code de retour.

lments de programmation shell CHAPITRE 4

125

On prendra bien note que le code de retour ne sert que dans les tests (par exemple, if) mais quil ne peut normalement pas tre stock dans une variable, ou afch. Il nest pas utilis pour transmettre un rsultat, mais bien une condition de russite ou dchec. Le code de retour correspond une valeur vraie sil est nul, et fausse sinon. En voici une illustration :
fonction_3.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #! /bin/sh function renvoie_un () { return 1 } function renvoie_zero () { return 0 } echo -n "renvoie_un : " if renvoie_un ; then echo vrai ; else echo faux ; fi echo -n "renvoie_zero : " if renvoie_zero ; then echo vrai ; else echo faux ; fi

Ce script se droule ainsi :


$ ./fonction_3.sh renvoie_un : faux renvoie_zero : vrai $

Lorsquune fonction doit renvoyer une valeur qui doit tre utilise par la suite, par exemple stocke dans une variable, deux possibilits soffrent nous : La fonction peut crire son rsultat sur sa sortie standard grce la commande echo, la valeur pouvant tre rcupre grce loprateur $() que nous avons rencontr au chapitre prcdent. La routine peut transmettre son rsultat dans une variable globale (surtout pas locale !) qui, par convention, prendra le mme nom que la fonction. Ces deux mthodes sont illustres dans le script suivant :
fonction_4.sh: 1 2 3 4 #! /bin/sh function carre {

126

Shells Linux et Unix par la pratique

5 6 7 8 9 10 11 12 13 14 15 16 17 18

echo $(($1 * $1)) } function cube { cube=$(($1 * $1 * $1)) } x=$1 x2=$(carre $x) cube $x x3=$cube echo "x=$x x^2=$x2 x^3=$x3"

dont lexcution donne :


$ ./fonction_4.sh 2 x=2 x=3 $ x^2=4 x^2=9 x^3=8 x^3=27 $ ./fonction_4.sh 3

On peut trs bien envisager, selon le mme principe, de renvoyer des rsultats sous forme de chanes de caractres :
$ function elimine_chemin > { > > } $ elimine_chemin /usr/src/linux/README $ echo $elimine_chemin README $ function elimine_suffixe > { > > } $ base=$(elimine_suffixe fichier.c) $ echo $base fichier $ echo ${1%%\.*} elimine_chemin=${1##*/}

lments de programmation shell CHAPITRE 4

127

Enn, indiquons que Bash et Ksh sont compatibles avec la notion dalias. Il sagit dun concept ancien moins performant que les fonctions, puisque cette notion ne permet pas dutiliser directement des arguments. Lutilisation des alias est prsent dconseille, et on leur prfre les fonctions.

Conclusion
Nous avons pu mettre en place dans ce chapitre les mcanismes qui permettent de faire fonctionner des programmes shell. Nous avons tudi en dtail les commandes et leur composition, ainsi que les structures de contrle indispensables lcriture dalgorithmes. Il devient vident que les possibilits des scripts shell sont loin dtre ngligeables et que nous allons pouvoir crire des programmes vraiment intressants. En prsentant les fonctions et les utilitaires sur lesquels nous pourrons asseoir nos scripts, le prochain chapitre va dvelopper la vraie dimension de ces scripts.

Exercices
4.1 Redirection de lentre standard (facile)
Pourquoi ls trouve-t-il toujours les mmes chiers lorsquon invoque les commandes suivantes ?

ls < /dev ls < /etc ls < /usr/bin

4.2 Redirection vers chier inaccessible (facile)


Sur de nombreux systmes Unix actuels, le chier /etc/shadow masque les mots de passe et nest pas accessible mme en lecture pour les utilisateurs courants. Nous avons vu dans lexercice prcdent que ls ne lit pas son entre standard. Que se passe-t-il alors si nous invoquons la ligne suivante ? Pourquoi ?

$ ls < /etc/shadow
N. B. : sil ny a pas de chier /etc/shadow sur votre systme, crez un chier interdit en lecture ainsi :

$ touch my_shadow $ chmod 000 my_shadow $

128

Shells Linux et Unix par la pratique

4.3 Redirection pour une commande inexistante (facile)


Que se passe-t-il si on essaye dexcuter une commande inexistante en ayant redirig sa sortie standard vers un chier ?

4.4 Affectation temporaire de variable (moyen)


Essayez de prvoir le rsultat des commandes suivantes, puis excutez-les. Que se passe-t-il et pourquoi ?

$ VAR=1234 $ echo $VAR $ VAR=5678 echo $VAR $ echo $VAR

4.5 Structures de boucle for-do-done (facile)


crivez un script qui afche le nom de chaque chier du rpertoire dans lequel il se trouve, en le faisant prcder dun numro dordre, comme dans lexemple suivant :
$ ./numerote_fichiers.sh 1) boucle.sh 2) numerote_fichiers.sh 3) test_fichier.sh $
Utilisez pour cela une structure for-do-done.

4.6 Structures de boucle while-do-done (facile)


Ce second exercice va vous permettre de vous familiariser avec les structures de boucle while, que lon utilise trs frquemment dans les scripts shell, pour les itrations (comptage, etc.). crivez un script qui afche cinq reprises le contenu de sa ligne de commande, en dormant une seconde entre chaque afchage. Pour endormir un script, on invoque la commande Unix sleep.

lments de programmation shell CHAPITRE 4

129

Invoquez trois exemplaires de ce script en parallle (sur la mme ligne de commande), chacun avec un argument diffrent pour vrier que les excutions sont bien simultanes, les critures tant enchevtres, ainsi :

$ ./boucle.sh AAA & ./boucle.sh BBB & ./boucle.sh CCC AAA CCC BBB AAA BBB CCC AAA BBB CCC AAA CCC BBB AAA CCC BBB $
(Les messages concernant le contrle des jobs ont t supprims de lexcution ci-dessus.)

4.7 Tests des caractristiques dun chier (moyen)


Cet exercice va vous permettre dutiliser les nombreuses options de test offertes par le shell pour dterminer les caractristiques dun chier. crivez un script qui utilise les options de la commande test (ou []) pour dcrire les chiers qui lui sont passs en argument. Les options standards sont dcrites dans la page de manuel test(1).

5
Commandes, variables et utilitaires systme
Nous avons tudi en dtail les structures offertes par le langage de programmation des scripts shell, ainsi que les mcanismes dvaluation des expressions. Tout cela ne serait pourtant pas dune grande utilit sans lexistence de fonctions utilitaires sur lesquelles il convient dappuyer nos programmes. Nous savons en effet remplir et valuer des variables, faire des boucles ou slectionner des instructions, mais nous navons pas tudi les moyens qui sont notre disposition pour interfacer notre script avec le systme. Il est important, quel que soit le langage de programmation tudi, de bien distinguer ce qui fait partie du langage lui-mme (mots-cls, directives, symboles) de ce qui relve de facilits externes offertes par le systme ou des bibliothques de fonctions. Cette distinction est par exemple trs marque avec le langage C o il est ncessaire de rclamer explicitement linclusion des dclarations des routines externes y compris les oprations dentres-sorties de base comme printf(), scanf(), ou fgets() pour viter les plaintes du compilateur. linverse, avec certains langages, le Basic par exemple, il est plus difcile de diffrencier les oprations qui appartiennent une bibliothque systme des mots-cls du langage. Le shell offre quelques commandes internes qui peuvent tre assimiles des fonctions de bibliothques. Ce rle est toutefois complt pour lessentiel par des utilitaires systme standards que lon retrouve normalement sur tout systme Unix et qui effectuent les tches les plus importantes.

132

Shells Linux et Unix par la pratique

Commandes internes
Les commandes internes du shell peuvent se rpartir en trois catgories essentielles : la conguration du comportement du shell ; lexcution de scripts ; les interactions avec le systme. Il convient dy ajouter les commandes internes relatives lutilisation du shell en mode interactif (historique, contrle des jobs, conguration du clavier, etc.) que nous ntudierons pas, car nous nous limitons la programmation de scripts. Il existe galement des fonctions avances (traitement des signaux, gestion du paralllisme, etc.) moins utiles pour les scripts courants.

Comportement du shell
set

Le comportement du shell est largement congurable laide de la commande set. Cette dernire comporte un nombre important doptions. On pourra consulter la page de manuel de Bash ou Ksh pour en connatre le dtail. Les plus intressantes, en ce qui concerne la programmation, sont les suivantes :
Option Signication
Toutes les variables cres ou modies seront automatiquement exportes dans lenvironnement des processus ls. Ne pas excuter les commandes du script mais les afcher pour permettre le dbogage.

-a ou -o allexport -n ou -o noexec -u ou -o nounset -v ou -o verbose -x ou -o xtrace

La lecture du contenu dune variable inexistante dclenchera une erreur. Cela est trs prcieux pour la mise au point des scripts. Afcher le contenu du script au fur et mesure de son excution.

Permettre le suivi du script instruction par instruction. Nous dcrirons cela dans le prochain chapitre.

Lutilisation dun + la place du - devant une option permet de rclamer le comportement oppos. Il est conseill de commencer systmatiquement un script par set-u pour dtecter immdiatement les fautes de frappe dans lcriture du nom dune variable.

Commandes, variables et utilitaires systme CHAPITRE 5

133

Excution des scripts et commandes


source

Le lancement dun script ou dune commande externe peut se faire de plusieurs manires. Tout dabord, on peut appeler directement le programme, sur la ligne de commande ou dans une ligne de script, si le chier se trouve dans un rpertoire mentionn dans la variable PATH, ou si le chemin daccs est indiqu en entier. Dans ce cas, un processus ls est cr, grce lappel systme fork(), et ce nouveau processus va excuter la commande indique. Ds que celle-ci se termine, le processus ls meurt galement. Le processus pre attend la n de son ls et en reoit un code dont il tient compte ou non expliquant les circonstances de la terminaison du processus. La cration dun processus ls peut tre mise en vidence grce la variable spciale $$ qui, nous le verrons plus bas, contient le PID (Process Identier) du processus.
affiche_pid.sh: 1 2 #! /bin/sh echo "PID du shell : $$"

Lexcution montre bien que le numro de PID volue au fur et mesure des invocations :
$ echo $$ 2199 $ ./affiche_pid.sh PID du shell : 2273 $ ./affiche_pid.sh PID du shell : 2274 $ ./affiche_pid.sh PID du shell : 2275 $

Dans le cas dun script shell, on peut galement demander que lexcution ait lieu dans lenvironnement en cours, le shell lisant le script ligne ligne et linterprtant directement. Pour cela, on utilise la commande source, que lon peut galement abrger en un simple point. Cela prsente deux avantages essentiels : le script excut peut modier lenvironnement du shell courant, aussi bien les variables que le rpertoire de travail ; le chier na pas besoin dtre excutable. Ce second point mrite quelques explications : sur la plupart des systmes Linux, on laisse la possibilit un utilisateur quelconque de monter une cl USB ou un CD-Rom dans larborescence des chiers. Toutefois, pour viter les problmes de scurit, ladministrateur interdit gnralement lexcution des chiers qui se trouvent sur ces supports. Il devient plus difcile dans ces conditions de fournir une application sur un CD ou une cl USB avec un script qui automatise toute linstallation, puisquil faudrait demander au

134

Shells Linux et Unix par la pratique

destinataire de copier le script dans un rpertoire temporaire, de le rendre excutable puis de le lancer, ou encore de remonter le support avec loption -oexec de mount. Par chance, lutilisation de source vient notre aide et nous pouvons prvoir un script qui assurera toutes les manipulations ncessaires linstallation, et demander simplement lutilisateur de faire quelque chose comme :
mount /mnt/cdrom source /mnt/cdrom/install umount /mnt/cdrom

Il faut toutefois penser ceci : si un script excut avec source se termine par une instruction exit que nous dtaillons ci-dessous cest le shell lui-mme qui sachve. Lutilisation de source est rserve aux scripts shell, comme en tmoigne lexemple suivant, dans lequel on remarque que le script affiche_pid.sh garde bien le mme numro de PID que le shell :
$ echo $$ 2199 $ source affiche_pid.sh PID du shell : 2199 $ . affiche_pid.sh PID du shell : 2199 $ ./affiche_pid.sh PID du shell : 2345 $ source /bin/ls source: /bin/ls: cannot execute binary file $

exec

Il est malgr tout possible de demander, grce la commande interne exec, quun chier binaire soit excut par le mme processus que le shell. ce moment, toute limage mmoire du shell est remplace dnitivement par celle de lexcutable rclam. Lorsque le programme se terminera, le processus mourra sans revenir au shell :
$ exec /bin/date mer dc 6 13:59:03 CET 2000 (fin de la connexion)

Il y a une autre faon dutiliser exec pour modier les redirections dentres-sorties en cours de script. Lorsque exec na pas dargument, les ventuelles redirections sont mises en place, et le script continue normalement. Voici un aperu de ce mcanisme :

Commandes, variables et utilitaires systme CHAPITRE 5

135

exemple_exec.sh: 1 2 3 4 #! /bin/sh echo "Cette ligne est crite avant le exec" exec > sortie.txt echo "Cette ligne est crite aprs le exec"

La redirection de la sortie standard en cours de script est bien applique :


$ ./exemple_exec.sh Cette ligne est crite avant le exec $ cat sortie.txt Cette ligne est crite aprs le exec $ rm sortie.txt $

exit

Il est parfois ncessaire de terminer un script shell brutalement, sans attendre darriver la dernire instruction, ou depuis lintrieur dune fonction, comme cest le cas dans la gestion derreurs critiques. Une commande interne, nomme exit, termine immdiatement le shell en cours, en renvoyant le code de retour indiqu en argument. Si ce code est absent, exit renvoie le code de la dernire commande excute. Par convention, un code nul correspond une russite, et prend une valeur vraie dans un test if. Un code non nul signie chec et renvoie une valeur fausse pour if. Le script suivant vrie sil a bien reu un argument et sil sagit bien dun chier avant dinvoquer la commande file qui afche le type du chier.
exemple_exit: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #! /bin/sh function verifie_fichier { if [ ! -f $1 ] ; then echo "$1 n'existe pas !" exit 1 fi } if [ $# -ne 1 ]; then echo "Syntaxe : $0 fichier" exit 1 fi verifie_fichier $1 file $1 exit 0

136

Shells Linux et Unix par la pratique

Nous pouvons tester les trois exit possibles :


$ ./exemple_exit.sh Syntaxe : ./exemple_exit.sh fichier $ ./exemple_exit.sh /etc/passwords /etc/passwords n'existe pas ! $ ./exemple_exit.sh /etc/passwd /etc/passwd: ASCII text $

Pour vrier les codes de retour, nous devons encadrer lappel par un test :
$ if ./exemple_exit.sh ; then echo Vrai ; else echo Faux ; fi Syntaxe : ./exemple_exit.sh fichier Faux $ if ./exemple_exit.sh /etc/pass; then echo Vrai; else echo Faux; fi /etc/pass n'existe pas ! Faux $ if ./exemple_exit.sh /etc/passwd; then echo Vrai; else echo Faux; fi /etc/passwd: ASCII text Vrai $

On notera toutefois que lappel exit termine entirement le shell en cours. Lors dune invocation classique, le processus ls cr pour excuter le script se nit et le processus pre reprend la main. En revanche, lors dune invocation avec source ou exec, le shell original est galement termin.

Interactions avec le systme


Rpertoire de travail

Chaque processus dispose dun rpertoire de travail. Lorsquun utilisateur se connecte, le shell est lanc dans son rpertoire personnel indiqu dans le chier /etc/passwd. Pour changer de rpertoire, on utilise la commande interne cd. Il sagit ncessairement dune commande interne et pas dun utilitaire systme /bin/cd, car lexcution dans un processus ls ne permettrait pas de modier le rpertoire de travail du processus pre. La commande cd a quelques particularits intressantes : cd seul revient dans le rpertoire indiqu dans la variable HOME congure lors de la connexion de lutilisateur. Si cette variable nexiste pas, cd ne fait rien. cd- ramne dans le rpertoire prcdent. On peut alterner ainsi entre deux rpertoires avec des invocations cd- successives.

Commandes, variables et utilitaires systme CHAPITRE 5

137

Lorsque largument de cd nest pas un chemin complet, le sous-rpertoire de destination est recherch par des balayages des rpertoires indiqus dans la variable CDPATH. Celle-ci nest que rarement congure. Lorsquelle nexiste pas, cd considre quelle vaut . : cest--dire quil ne recherche la destination que dans les sous-rpertoires du rpertoire courant. Inversement, pour retrouver le nom du rpertoire courant, on peut recourir une commande interne nomme pwd qui fournit ce nom sur sa sortie standard. Il est prfrable demployer cette commande car elle est optimise, mme sil existe un utilitaire systme /bin/pwd.
Entres-sorties echo

Nous avons dj largement utilis la commande echo qui permet un script denvoyer sur la sortie standard un message constitu de ses arguments mis bout bout. Il sagit dune commande interne du shell, mais il en existe souvent un quivalent sous forme dexcutable binaire /bin/echo. Les options reconnues par la version Gnu (Linux) de echo et de son implmentation interne dans Bash sont :
Option echo interne

/bin/echo

Signication
Ne pas effectuer de saut de ligne aprs lafchage. Cela permet de juxtaposer des messages lors dappels successifs, ou dafcher un symbole invitant lutilisateur une saisie. Interprter les squences particulires de caractres, dcrites plus bas. Ne pas interprter les squences spciales. Il sagit du comportement par dfaut. Afcher le numro de version de lutilitaire. Afcher un cran daide.

-n

-e -E --version --help

Les deux versions de echo peuvent interprter des squences spciales de caractres lorsquelles les rencontrent dans les chanes darguments. Cela nest effectu que si loption -e est prcise. Les squences sont rsumes dans le tableau suivant :
Squence Signication
Afchage du caractre \ littral Afchage du caractre dont le code Ascii est XXX exprim en octal Sonnerie Retour en arrire dun caractre liminer le saut de ligne nal (comme loption -n)

\\ \XXX \a \b \c

138

Shells Linux et Unix par la pratique

Squence

Signication
Saut de page Saut de ligne Retour chariot en dbut de ligne Tabulation horizontale Tabulation verticale

\f \n \r \t \v

On peut utiliser ces squences pour contrler grossirement le mouvement du curseur. Par exemple, la commande :
while true ; do D=$(date) ; echo -en "\r$D " ; done

afche la date en permanence en bas gauche de lcran, chaque criture crasant la prcdente.
read

Un script doit non seulement envoyer des rsultats sur la sortie standard, mais aussi pouvoir lire les saisies de lutilisateur depuis lentre standard. Cette tche est ralise par la fonction interne read. Elle prend en argument un ou plusieurs noms de variables, lit une ligne depuis lentre standard, puis la scinde en diffrents mots pour remplir les variables indiques. Le dcoupage a lieu en utilisant comme sparateurs les caractres prsents dans la variable spciale IFS. Le premier mot trouv est plac dans la premire variable, le suivant dans la deuxime et ainsi de suite, jusqu la dernire variable qui recevra tout le restant de la ligne. Si aucun nom de variable nest indiqu, tout le contenu de la ligne est affect la variable spciale REPLY. La fonction read est interne au shell ; il ne serait pas possible dutiliser un chier excutable binaire /bin/read qui serait excut dans un sous-processus et ne modierait donc pas les variables denvironnement du shell pre. Pour la mme raison, il ne faut pas utiliser read dans un pipeline, car chaque commande peut tre excute dans un processus indpendant. Supposons par exemple que nous souhaitions retrouver le nom complet de lutilisateur partir de son nom de connexion en consultant le chier /etc/passwd. Ce dernier est constitu de lignes qui contiennent les champs suivants, spars par des deux-points : identiant ; mot de passe (gnralement absent car dissimul dans le chier /etc/shadow) ; numro didentication de lutilisateur ; numro didentication du groupe principal auquel appartient lutilisateur ; nom complet de lutilisateur ; rpertoire personnel de connexion ; shell utilis.

Commandes, variables et utilitaires systme CHAPITRE 5

139

Nous pourrions tre tents dcrire :


IFS=":" ; grep $USER /etc/passwd | read ident passe uid gid nom reste

Hlas, cela ne fonctionne pas de manire portable car read est excut dans un sousprocessus et ne modie pas la variable ident du shell pre. Le plus gnant cest que cette commande fonctionne sur certaines versions du shell Korn. Ce dernier considre quun read en dernire position dun pipeline doit tre excut par le shell pre. Toutefois cette criture ne sera absolument pas portable. Une solution consiste stocker le rsultat des commandes en amont de read dans une variable, puis assurer la lecture avec une entre standard redirige depuis cette variable au moyen dun document en ligne :
lit_nom.sh: 1 2 3 4 5 6 7 8 #! /bin/sh A=$(grep $USER /etc/passwd) IFS=":" read ident passe uid gid nom restant <<FIN $A FIN echo "Le nom de $ident est : $nom"

Lcriture du script est un peu complique premire vue, mais cela fonctionne comme on le souhaitait :
$ ./lit_nom.sh Le nom de cpb est : Christophe Blaess $

Le comportement du shell par rapport la variable IFS est parfois un peu droutant. Il faut savoir quelle lui sert essentiellement sparer les mots obtenus sur une ligne, de faon distinguer les commandes et les arguments. Par dfaut, IFS contient les caractres espace, tabulation et retour chariot. Cette variable a t employe de nombreuses reprises pour exploiter des failles de scurit, aussi les shells modernes prennent-ils son encontre des mesures draconiennes : IFS nest jamais exporte dans lenvironnement des sous-processus. IFS est rinitialise sa valeur par dfaut chaque dmarrage du shell (elle nest donc pas importe). Voici une autre explication au fait que la ligne nave de lecture du nom de lutilisateur depuis le chier /etc/passwd avec un pipeline ne fonctionne pas, IFS reprenant sa valeur par dfaut dans chacun des sous-processus.

140

Shells Linux et Unix par la pratique

On notera quil est galement possible dutiliser la commande cat pour effectuer des entres-sorties, surtout sagissant de messages qui doivent se prsenter sur plusieurs lignes. Voici un exemple de prsentation de menu :
menu.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #! /bin/sh cat << FIN ********************** * titre du programme * ********************** 1 - Premire option 2 - Seconde option 0 - Fin FIN while [ "$REPLY" != "0" ] ; do echo -n "Votre choix : " read case "$REPLY" in 1 ) echo "Option numro 1" ;; 2 ) echo "Option numro 2" ;; 0 ) echo "Au revoir" ;; * ) echo "Option invalide !" ;; esac done

$ ./menu.sh ********************** * titre du programme * ********************** 1 - Premire option 2 - Seconde option 0 - Fin Votre choix : A Option invalide ! Votre choix : 1 Option numro 1 Votre choix : 2 Option numro 2 Votre choix : 0 Au revoir $

Commandes, variables et utilitaires systme CHAPITRE 5

141

Lexcution prsente bien lcran voulu : Dune faon symtrique, cat peut servir lire dans une mme variable plusieurs lignes de texte en une seule fois. La saisie doit se terminer par le caractre EOF, qui sobtient en pressant Contrle-D moins davoir fait des choses peu recommandables avec la commande stty, dont nous reparlerons. Pour que le contenu de la variable soit afch sur plusieurs lignes, il faut lencadrer par des guillemets, comme cela a dj t expliqu dans le chapitre prcdent, sinon les arguments de echo sont tous regroups sur la mme ligne. Maintenant que lon sait que le caractre retour chariot est contenu dans la valeur par dfaut de IFS, on comprend pourquoi les lignes sont regroupes lors de linvocation de echo comme sil sagissait darguments juxtaposs sur la ligne de commande.
$ A=$(cat) Voici un texte qui s'tend sur plusieurs lignes (Contrle-D) $ echo "$A" Voici un texte qui s'tend sur plusieurs lignes $ echo $A Voici un texte qui s'tend sur plusieurs lignes $

Arguments en ligne de commande


La tradition impose aux utilitaires Unix de faire en sorte que lutilisateur puisse adapter leur comportement grce des options en ligne de commande. Pour tre tout fait exact, on recommande galement que le paramtrage soit possible paralllement, grce des variables denvironnement, mais que les options en ligne de commande aient priorit sur ces dernires. Les scripts shell nchappent pas cette rgle, et on peut souvent juger de leur qualit en valuant la gnralit de la tche effectue et la souplesse du comportement qui est congur au moyen des options en ligne de commande. Lanalyse correcte des options nest pas une tche trs aise, dune part parce que plusieurs dentre elles peuvent tre regroupes (ls-alF au lieu de ls-a-l-F) et, dautre part, parce que certaines seulement prennent un argument. Il vaut donc mieux viter davoir rcrire ces routines dans chaque script, et le shell nous propose directement une fonction de haut niveau nomme getopts.

142

Shells Linux et Unix par la pratique

Nous avons dj rencontr getopts dans le chapitre 2, pour la lecture des options du script rm_secure. Sa mise en uvre est en gnral la suivante :
while getopts liste_d_options option; do case $option in option_1 ) ... ;; option_2 ) ... ;; ? ) ... ;; esac done

Le premier argument qui se trouve la suite de getopts est une chane contenant toutes les lettres acceptes par le script pour introduire une option. Si une option doit tre son tour complte par un argument, on fait suivre la lettre dun deux-points. Par exemple, les options de la commande touch reconnues par les systmes SUSv3 comprennent au moins :
Option Signication
Mettre jour la date et lheure du dernier accs au chier. Ignorer les chiers qui nexistent pas. Mettre jour la date et lheure de dernire modication du chier. Utiliser la date et lheure du chier indiqu plutt que la date actuelle. Utiliser la date et lheure fournies plutt que la date actuelle.

-a -c -m -r fichier -t date

On peut crire comme ceci la chane qui dcrit les options : acmr:t:. chaque appel, la fonction getopts incrmente la variable interne OPTIND qui vaut zro par dfaut au dbut du script, et examine largument de la ligne de commande ayant ce rang. Sil sagit dune option simple, qui ne rclame pas dargument, elle place la lettre correspondante dans la variable dont le nom a t fourni la suite de la chane doptions. Sil sagit dune option qui ncessite un argument supplmentaire, elle en vrie la prsence, et le place dans la variable spciale OPTARG. La fonction getopts renvoie une valeur vraie si une option a t trouve, et fausse sinon. Ainsi, aprs lecture de toutes les options qui se trouvent en dbut de ligne de commande, on peut accder aux arguments qui suivent, simplement en sautant les OPTIND-1, premiers mots grce linstruction shift. Lorsquune option illgale est rencontre, getopts remplit la variable doption avec un point dinterrogation. La description du fonctionnement de getopts est un peu complexe premire lecture, mais son utilisation est en fait plus simple quil ny parat. Le script suivant met en pratique ces premires tapes :
exemple_getopts_1.sh: 1 2 3 #! /bin/sh while getopts "abc:d:" option ; do

Commandes, variables et utilitaires systme CHAPITRE 5

143

4 5 6 7 8 9 10 11 12 13 14 15 16 17

echo -n "Analyse argument numro $OPTIND : " case $option in a ) echo "Option A" ;; b ) echo "Option B" ;; c ) echo "Option C, argument $OPTARG" ;; d ) echo "Option D, argument $OPTARG" ;; ? ) echo "Inconnu" ;; esac done shift $((OPTIND - 1)) while [ $# -ne 0 ] ; do echo "Argument suivant : " $1 shift done

Les options a et b ne ncessitent pas darguments supplmentaires, contrairement aux options c et d. Sil reste des lments sur la ligne de commande, une fois que toutes les options sont analyses, on les afche tels quels :
$ ./exemple_getopts_1.sh -a -c arg1 suite ligne Analyse argument numro 2 : Option A Analyse argument numro 4 : Option C, argument arg1 Argument suivant : suite Argument suivant : ligne $ ./exemple_getopts_1.sh -bad arg1 -c arg2 Analyse argument numro 1 : Option B Analyse argument numro 2 : Option A Analyse argument numro 3 : Option D, argument arg1 Analyse argument numro 5 : Option C, argument arg2 $ ./exemple_getopts_1.sh -zb et fin ./exemple_getopts_1.sh: illegal option -- z Analyse argument numro 1 : Inconnu Analyse argument numro 2 : Option B Argument suivant : et Argument suivant : fin $

Nous remarquons que les options peuvent tre regroupes dans nimporte quel ordre et que getopts afche un message en cas doption inconnue (-z dans le dernier exemple). Ce comportement peut tre congur de deux manires diffrentes qui ne donnent pas tout

144

Shells Linux et Unix par la pratique

fait le mme rsultat. Par dfaut, en cas derreur (option inconnue ou argument supplmentaire manquant), le shell afche un message et place le caractre ? dans la variable doption :
$ ./exemple_getopts_1.sh -z ./exemple_getopts_1.sh: illegal option -- z Analyse argument numro 2 : Inconnu $ ./exemple_getopts_1.sh -c ./exemple_getopts_1.sh: option requires an argument -- c Analyse argument numro 2 : Inconnu $

Lorsque la variable OPTERR contient la valeur 0, le shell nafche pas de message derreur, place le caractre ? dans la variable doption et efface la variable OPTARG. Le script suivant met ce comportement en avant :
exemple_getopts_2.sh: 1 2 3 4 5 6 7 8 9 10 11 12 #! /bin/sh OPTERR=0 while getopts "abc:d:" option ; do case $option in a ) echo "Option -a" ;; b ) echo "Option -b" ;; c ) echo "Option -c, argument $OPTARG" ;; d ) echo "Option -d, argument $OPTARG" ;; ? ) echo "Option inconnue" ;; esac done

Cette fois, les messages ne sont plus prsents :


$ ./exemple_getopts_2.sh -z Option inconnue $ ./exemple_getopts_2.sh -d Option inconnue $

Nous pouvons observer que le comportement est identique pour deux cas derreur diffrents. Cela est un peu gnant en termes de dialogue avec lutilisateur, aussi le shell propose-t-il de diffrencier les deux situations. Lorsque le premier caractre de la chane

Commandes, variables et utilitaires systme CHAPITRE 5

145

contenant les options est un deux-points, getopts nafche aucun message derreur, mais adopte le comportement suivant : 1. lorsquune lettre doption illgale est rencontre, le caractre ? est plac dans la variable doption, et la lettre est place dans OPTARG ; 2. si un argument supplmentaire est manquant, le caractre : est plac dans la variable doption, et la lettre de loption qui ncessite largument est place dans OPTARG.
exemple_getopt_3.sh: 1 2 3 4 5 6 7 8 9 10 11 12 #! /bin/bash2 while getopts ":abc:d:" option ; do case $option in a ) echo "Option -a" ;; b ) echo "Option -b" ;; c ) echo "Option -c, argument $OPTARG" ;; d ) echo "Option -d, argument $OPTARG" ;; : ) echo "Argument manquant pour l'option -$OPTARG" ;; ? ) echo "Option -$OPTARG inconnue" ;; esac done

Nous voyons que les situations sont bien diffrencies, les messages derreur tant ainsi plus explicites :
$ ./exemple_getopts_3.sh -a -z Option -a Option -z inconnue $ ./exemple_getopts_3.sh -c Argument manquant pour l'option -c $

La fonction interne getopts nest pas limite la lecture des arguments dun script. On peut lutiliser directement pour lire les arguments dune fonction. Cela prsente un intrt, surtout lorsque cette fonction est exporte dans lenvironnement, comme pour le remplacement de rm au chapitre 2. Dans ce cas, les variables ntant pas rinitialises par un lancement du shell, il est important de ramener OPTIND zro avant la premire invocation de getopts. Il nest pas possible avec getopts de grer directement les options longues la manire des utilitaires GNU (--verbose par exemple) mais, comme nous lavions dj voqu prcdemment, un artice permet de les proposer quand mme dans un script shell. On peut fournir getopts, dans sa chane doptions, le caractre -, suivi dun deuxpoints. Lorsquil rencontrera help, par exemple, il considrera que - est la lettre doption, suivie dun argument help. On peut assurer une interprtation en deux temps

146

Shells Linux et Unix par la pratique

des options, en dcryptant dabord les options longues, puis les options courtes. La manire la plus simple de procder est, lors du dcodage dune option longue, de remplir la variable doption avec la lettre de loption courte correspondante. Ainsi le vritable travail naura-t-il lieu quune fois. Le script suivant applique ce principe avec deux options longues --help et --version qui correspondent aux options courtes -h et -v.
exemple_getopts_4.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #! /bin/sh VERSION=3.14 while getopts ":hv-:" option ; do if [ "$option" = "-" ] ; then case $OPTARG in help ) option=h ;; version ) option=v ;; * ) echo "Option $OPTARG inconnue" ;; esac fi case $option in h ) echo "Syntaxe : $(basename $0) [option...]" echo " Options :" echo " -v --version : Numro de version" echo " -h --help : Cet cran d'aide" ;; v ) echo "Version $(basename $0) $VERSION" ;; ? ) echo "Inconnu" ;; esac done

Nous pouvons invoquer les options indpendamment, en version longue ou courte :


$ ./exemple_getopts_4.sh -h Syntaxe : exemple_getopts_4.sh [option...] Options : -v --version : Numro de version -h --help : Cet cran d'aide $ ./exemple_getopts_4.sh -v --help Version exemple_getopts_4.sh 3.14 Syntaxe : exemple_getopts_4.sh [option...] Options : -v --version : Numro de version -h --help : Cet cran d'aide $ ./exemple_getopts_4.sh --version Version exemple_getopts_4.sh 3.14 $

Commandes, variables et utilitaires systme CHAPITRE 5

147

Lutilisation des options longues permet de documenter automatiquement linvocation dun utilitaire dans un script, et amliore donc lergonomie de lapplication. Il faut quand mme tre conscient que leur implmentation, telle que nous lavons dcrite ici, ne permet pas de disposer de toutes les fonctionnalits rellement offertes par les routines danalyse de la ligne de commande prsentes dans la bibliothque C. Entre autres, on ne peut pas facilement proposer doptions longues qui acceptent des arguments supplmentaires.

Variables internes
Le shell gre automatiquement un nombre assez important de variables internes qui fournissent des informations aux scripts, ou permettent de modier certains aspects du comportement du shell. Nous allons en examiner quelques-unes qui prsentent un intrt particulier pour la cration de scripts. Le lecteur pourra se reporter la documentation en ligne de Bash ou Ksh pour obtenir plus dinformation sur les variables qui servent congurer le comportement interactif du shell.
$?

Le paramtre $? contient le code de retour du dernier pipeline excut lavant-plan. Ceci nous permet de rcuprer le code de retour dune commande, et de lanalyser en dtail. Par exemple, la commande ping envoie des demandes dcho lhte indiqu sur sa ligne de commande. Si elle reoit bien la rponse en cho, elle se termine avec un code de retour nul. Si elle ne reoit aucune rponse, alors quun dlai maximal est indiqu, elle renvoie 1; en cas dautre erreur elle renvoie 2. Utilisons ceci pour afcher un message explicite :
code_ping.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #! /bin/sh while [ -n "$1" ]; do # on envoie un seul paquet et on # attend au plus deux secondes ping -c 1 -w 2 $1 > /dev/null 2>&1 resultat=$? # .... if [ $resultat = 0 ]; then echo "$1 Ok !" elif [ $resultat = 1 ]; then echo "$1 injoignable" else echo "$1 inexistant" fi shift done

148

Shells Linux et Unix par la pratique

Le rsultat mmoris en ligne 7 pourrait tre trait bien plus loin dans le script, sans que les commandes excutes entre temps (ce que symbolisent les points de suspension de la ligne 8) ninterfrent. Lexcution donne :
$ ./code_ping.sh 192.1.1.52 192.1.1.52 Ok ! $ ./code_ping.sh 192.1.1.54 192.1.1.54 injoignable $ ./code_ping.sh 192.1.1.256 192.1.1.256 inexistant $

$$, $!, PPID

Le paramtre $$ contient le PID (Process Identier) du shell en cours. Il ne sagit pas ncessairement du PID de la commande qui est en train de sexcuter, mais bien de celui du shell principal. Lorsquun sous-shell est invoqu entre parenthses, le contenu de $$ nest pas modi, mme si nous protgeons la chane avec des apostrophes pour viter linterprtation directe du nom du paramtre. Pour quil change, il faut quun nouveau shell soit effectivement invoqu :
$ echo $$ 894 $ (echo $$) 894 $ (eval 'echo $$') 894 $ eval 'echo $$' | cat 894 $ sh -c 'echo $$' 1709 $

Le paramtre $! contient le PID de la dernire commande excute en arrire-plan. Nous lutiliserons dans le prochain chapitre en tudiant le paralllisme des commandes. La variable PPID qui nest pas modiable contient le PPID (Parent PID), cest--dire le PID du processus pre du shell en cours. Le fonctionnement qui en rsulte par rapport aux sous-shells est identique celui de $$.

Commandes, variables et utilitaires systme CHAPITRE 5

149

$ echo $PPID 893 $ (eval 'echo $PPID') 893 $ echo $$ 894 $ sh -c 'echo $PPID' 894 $

UID, EUID

Ces deux variables, accessibles uniquement en lecture, contiennent respectivement lUID (User Identier) rel et lUID effectif du shell. LUID rel est le numro didentication de lutilisateur qui a lanc le shell. LUID effectif est lidentication qui est prise en considration par le noyau pour vrier les autorisations daccs aux divers lments du systme (et en premier lieu aux chiers). Dans la plupart des cas, ces deux variables doivent contenir la mme valeur. Elles diffrent lorsquun processus sexcute avec des privilges plus levs (en gnral) que ceux dont dispose normalement lutilisateur qui la lanc. Cela permet de fournir un accs des lments critiques du systme avec un contrle bien particulier. Pour que cela soit possible, les bits dautorisation du chier excutable du processus doivent inclure un bit particulier nomm Set-UID. Lutilisation de scripts Set-UID tant en soi une faille de scurit sur de nombreux systmes, Linux ne tient aucun compte de ce bit pour les scripts. La seule manire dinvoquer un script avec un UID effectif, diffrent de lUID rel, est de lappeler depuis lintrieur dun programme C compil, disposant lui-mme du bit Set-UID. Il nest gnralement pas utile de se soucier de ces variables mais, dans le cas o cest indispensable, on utilisera UID pour identier lutilisateur qui dialogue avec le script et EUID pour connatre lidentit prise en compte pour les autorisations daccs.
HOME, PWD, OLDPWD

La variable HOME contient le rpertoire personnel de lutilisateur. Cest lendroit o lon revient automatiquement lorsquon invoque la commande cd sans argument. La variable PWD contient le rpertoire de travail courant du shell. Cette variable est mise jour lorsquon invoque la commande interne cd. La variable OLDPWD renferme le rpertoire prcdent, celui vers lequel on retourne en invoquant cd-.

150

Shells Linux et Unix par la pratique

La fonction interne pwd afche le chemin daccs absolu au rpertoire courant partir de la racine du systme de chiers. On peut samuser en dnir une version qui afche autant que possible le chemin daccs partir du rpertoire HOME symbolis par le caractre ~. En voici une implmentation qui joue sur lextraction du motif contenu dans HOME en tte de variable PWD. On procde en deux tapes pour vrier si la chane est trouve ou non :
pwd.sh: 1 2 3 4 5 6 7 #! /bin/sh function pwd () { A=${PWD%%$HOME*} echo ${A:-\~${PWD#$HOME}} }

Bien entendu, le script doit tre sourc pour que la fonction reste dans lenvironnement du shell interactif :
$ source pwd.sh $ pwd ~/Doc/ScriptLinux/Exemple/05 $ cd /etc $ pwd /etc $ cd $ pwd ~ $

Commandes externes
Les commandes internes offertes par le shell ne permettent pas de raliser toutes les oprations souhaitables dans un script. Il est souvent ncessaire de faire appel des fonctions externes gnralement programmes en C, qui peuvent dialoguer de faon plus complte avec le systme. Ces utilitaires standards sont disponibles sur la majeure partie des systmes Unix, mme si leurs options peuvent diffrer lgrement suivant les implmentations. La norme SUSv3 en dcrit une partie, ce qui leur confre une certaine portabilit. Le tableau prsent ci-aprs regroupe quelques utilitaires parmi les plus pratiques. On se reportera aux pages individuelles de chaque commande de manuel pour avoir des prcisions sur leur invocation.

Commandes, variables et utilitaires systme CHAPITRE 5

151

Nom

SUSv3 ?

Utilit
Diffrer lexcution dun programme. La commande at permet dindiquer lheure de dmarrage retenue pour une tche. batch attend que le systme ne soit pas trop charg. liminer le chemin daccs et un ventuel sufxe dans un nom de chier. Effectuer des calculs en virgule ottante. Nous en parlerons dans le prochain chapitre. Compresser ou dcompresser des chiers. Le taux de compression est souvent lgrement meilleur que celui atteint avec gzip. En revanche, la portabilit est moins bonne. Copier lentre standard vers la sortie standard. Peut servir afcher un texte en utilisant un document en ligne, ou saisir plusieurs lignes dans une seule variable. Calculer une somme de contrle dun chier. Cest utile lorsquon transfre des donnes par une liaison peu able (lien srie, disquette, etc.). md5sum emploie lalgorithme le plus complexe ; sum est le plus simple ; cksum est le plus portable des trois. Dcouper un chier en sections successives, dtermines par des lignes de sparation dont le contenu peut tre indiqu dans une expression rgulire. Afcher une date avec un format prcis. La conguration prcise du format permet dutiliser ce programme pour crer par exemple des noms de chiers denregistrement automatique. Afcher les diffrences entre deux chiers. Le programme diff effectue une analyse intelligente, ce qui permet dutiliser son rsultat pour disposer dun chier de variations que lon transmet lutilitaire patch an dobtenir les mmes volutions sur un autre systme. Envoyer un message sur la sortie standard. valuer une expression. Cet utilitaire prsente quelques fonctionnalits supplmentaires par rapport aux possibilits standards du shell. On lutilise surtout pour le traitement de chane, ou pour excuter des scripts avec un shell allg (systmes embarqus). Toujours renvoyer un code dchec. Sert tester un script en vriant le passage dans les diffrentes branches de lalgorithme. Afcher le type dun chier, en tudiant son contenu. Lanalyse permet de reconnatre un grand nombre de chiers binaires (image, son) et de sources de diffrents langages de programmation. Rechercher des chiers dans une arborescence. Nous reviendrons rapidement sur cet utilitaire dans le chapitre 7. Mettre un texte en forme en coupant les lignes pour quelles respectent une largeur donne . Les csures sont correctement gres. Couper les lignes dun texte moins intelligemment quavec fmt. Rechercher un motif dans des chiers. Nous examinerons cet utilitaire en dtail dans le chapitre 7. Compresser des chiers. Cet utilitaire est prsent sur la plupar t des Unix, mme sil est lgrement moins portable que compress. Afcher le dbut dun chier, cest--dire un nombre donn de lignes ou de caractres. Envoyer un signal un processus. Nous lexaminerons dans le prochain chapitre. Imprimer un chier. La conguration du systme dimpression dpend de ladministrateur, mais cette commande permet gnralement dimprimer au moins des chiers texte (sources) et la plupart du temps des chiers formats (PostScript, HTML, images, etc.) Modier la priorit dun processus. Cette commande permet de lancer un programme en lempchant de grignoter tous les cycles processeur, et de dranger ainsi les autres utilisateurs ou les autres processus. Son excution sera plus longue, mais plus courtoise en quelque sorte.

at batch basename bc bzip2 cat cksum md5sum sum csplit date diff

O O O O N O O N N O O O

echo expr

O O

false file find fmt fold grep gzip head kill lpr

O O O N O O N O O N

nice

152

Shells Linux et Unix par la pratique

Nom

SUSv3 ?

Utilit
Sparer un processus de son terminal de contrle. Nous examinerons cette commande dans le prochain chapitre. Prsenter le contenu dun chier sous forme de valeurs numriques dcimales, hexadcimales ou octales. Juxtaposer les lignes correspondantes provenant de plusieurs chiers. Lutilit de cette commande ne se prsente pas souvent. Prparer un chier de texte pour limpression. Cette commande gre entre autres la largeur et la numrotation des pages. Afcher des donnes formates. Cet utilitaire est une sorte decho nettement amlior proposant des formats pour afcher les nombres rels. Les programmeurs C reconnatront une implmentation en ligne de commande de la clbre fonction de la bibliothque stdio. Afcher la liste des processus en excution. Cette commande sera prsente dans le prochain chapitre. Dvelopper une liste de valeurs numriques. Cet utilitaire, rpandu mais pas inclus dans le standard SUSv3, prend en argument deux valeurs et envoie sur sa sortie standard une liste qui contient tous les nombres intermdiaires. Ainsi, seq110 afche tous les nombres de 1 10. On lutilise en association avec la commande for du shell. Trier les lignes dun chier. On peut congurer la portion de la ligne quil faut utiliser pour les comparaisons, ainsi que la mthode de tri. Dcouper un chier en plusieurs parties. Cet utilitaire permet le dcoupage de chiers binaires de sorte que leur recollage ultrieur avec cat reconstitue le chier original. Trs utile pour transporter un gros chier binaire laide de plusieurs suppor ts amovibles (cl USB). Congurer le terminal. Cet utilitaire permet dajuster nement le comportement du terminal (et bien souvent de le rendre temporairement inutilisable !). Nous y recourrons dans le prochain chapitre. Afcher les dernires lignes dun chier. Cela permet par exemple de voir les derniers enregistrements dun chier de journalisation (/var/log/messages). Loption -f permet un afchage continu chaque modication. Crer des archives regroupant plusieurs chiers. Cette commande tait utilise lorigine pour regrouper des chiers sur une bande, mais on lemploie prsent pour crer des archives dune arborescence (chiers source par exemple). La version GNU inclut des possibilits directes de compression/dcompression. Copier lentre standard vers la sortie standard et vers un chier. On insre parfois cet utilitaire dans un pipeline pour copier au passage les donnes dans un chier. Toucher un chier, ce qui modie ses horodatages. Cela sert dans des scripts dadministration qui utilisent les dates de dernier accs pour savoir si un chier peut tre effac. On lutilise aussi pour forcer la recompilation dun chier source avec make, ou pour crer un nouveau chier vide. Transposer des caractres lors de la copie de lentre standard vers la sortie standard. Cela permet entre autres de remplacer les caractres accentus par leur version nue, de traduire les caractres de contrle dune imprimante, ou de supprimer les caractres non imprimables. Toujours renvoyer un code de russite. Utilis pour tester un script, ou pour crire une boucle innie avec whiletrue; do. Compter les lignes, mots et caractres contenus dans un chier.

nohup od paste pr printf

O O O O O

ps seq

O N

sort split

O O

stty tail

O O

tar

tee touch

O O

tr

true wc

O O

Commandes, variables et utilitaires systme CHAPITRE 5

153

Conclusion
Nous avons prsent lessentiel des commandes internes du shell qui sont utilises dans les scripts, ainsi quune petite partie des utilitaires externes standards. Pour connatre la liste complte des commandes du shell, on peut se reporter la page de manuel de Bash ou Ksh. Pour obtenir plus de prcisions sur une commande donne, Bash propose la commande help. Par exemple, helpgetopts afche directement le passage du manuel relatif cette commande. Pour mieux connatre les utilitaires externes, la meilleure solution est dexaminer le contenu des rpertoires /bin et /usr/bin et dinvoquer la commande man sur chacun des chiers inconnus. Il est aussi possible, si on a install les pages de manuel traduites en franais, dinvoquer man1Index (Index avec une initiale majuscule) qui prsentera une ligne de description pour chaque page traduite dans la section Utilitaires systme .

Exercices
5.1 Comptage rebours (facile)
Cet exercice est assez proche de ceux du chapitre prcdent. Il est donc prfrable den proter pour se familiariser avec la structure de contrle for et la commande seq. crivez un script qui compte rebours de 5 0, avec une pause dune seconde chaque afchage. Ce type de comptage est frquemment utilis dans les scripts ayant une tche potentiellement dangereuse, an de laisser lutilisateur un dernier dlai de rexion pendant lequel il peut encore presser Ctrl-C pour annuler lopration.

5.2 Cration dun menu (plutt facile)


Crez un petit script qui afche un message proposant lutilisateur le choix entre une petite dizaine doptions et lit sa rponse en sassurant que la valeur saisie est bien dans la liste des options autorises. Votre script devra afcher un message dtaillant loption choisie avant de se terminer. Il existe de nombreuses manires de procder ; la solution propose la n du livre sappuie sur lutilisation dune redirection << (document en ligne) pour afcher le menu, et une structure case-esac pour analyser la rponse.

5.3 Saisie de rponse (facile)


Crez une fonction qui prenne en argument une chane de caractres reprsentant une question. Votre fonction devra afcher cette chane et attendre une rponse de lutilisateur.

154

Shells Linux et Unix par la pratique

Si la rponse est O (pour Oui), la fonction renverra une valeur Vrai, sinon elle renverra une valeur Faux. Appelez votre fonction une ou deux reprises dans le script pour tester son fonctionnement et afchez le code de retour de la fonction.

5.4 Saisie de rponse (suite) (facile)


Amliorons notre script : si lutilisateur saisit O la fonction renvoie Vrai ; sil saisit N, elle renvoie Faux ; dans tous les autres cas elle reboucle sur la question.

5.5 Saisie de rponse (suite) (plutt facile)


Amliorons encore notre script : la fonction renverra Vrai si lutilisateur saisit une chane de caractres qui commence par O, o, Y ou y, et Faux si elle commence par N ou n.

5.6 Saisie de rponse (suite) (plutt difcile)


Utilisons prsent notre fonction de saisie dans un script qui devine un nombre entre 1 et 1000 choisi par lutilisateur. Le script procdera par dichotomie et posera des questions auxquelles lutilisateur rpondra par Oui ou Non.

6
Programmation shell avance
Il est regrettable que les scripts shell ne soient bien souvent employs que pour des tches ingrates qui consistent pour lessentiel fournir les chiers dentre et de sortie aux applications proprement dites, auxquelles on rserve la partie noble du travail. Nous allons montrer dans ce chapitre que le shell permet daller bien plus loin, tant dans les fonctionnalits orientes systme (paralllisme, dmons, signaux, communication entre processus) que pour des applications volues (gestion des chiers, interface utilisateur interactive, calcul en virgule ottante).

Processus ls, paralllisme


La qualit du fonctionnement multitche dUnix reprsente lun des points les plus attractifs de ce systme dexploitation. On peut faire excuter simultanment plusieurs programmes sans quaucun deux ne ressente la prsence des autres, ou linverse en leur permettant de dialoguer entre eux. Le shell dispose naturellement de ces possibilits. Le paralllisme Unix bas niveau est fourni par le noyau qui duplique un processus lorsquon invoque lappel-systme fork(). Les deux processus sont alors strictement identiques, et seule la valeur de retour de fork() permet de les distinguer. Par analogie, nous crirons nos scripts shell de faon regrouper dans le mme script les deux processus et nous sparerons les deux branches dexcution au cours du programme. Pour quun programme shell se duplique, il suft quil se r-invoque lui-mme en faisant suivre lappel dun &. Le paramtre $0 contient le nom du programme ayant servi pour linvocation initiale, aussi lappel $0& suft-il pour lancer une nouvelle occurrence du mme script. Si on veut transmettre au processus ls les arguments reus sur la ligne de commande, on utilisera $0"$@"&.

156

Shells Linux et Unix par la pratique

Naturellement, le nouveau processus doit tre capable de dterminer quil est le ls dun script prcdent, an de ne pas relancer la duplication linni. Un moyen simple consiste remplir dans le processus pre, avant la duplication, une variable qui sera exporte dans lenvironnement du ls. Un test sur cette variable en dbut de programme permettra de scinder les deux branches dexcution. Nous savons dj que le paramtre spcial $$ contient le PID du shell qui excute le script en cours, mais un autre paramtre est intressant : $!, qui contient le PID de la dernire commande lance en arrire-plan. Comme nous ne lanons quun seul ls, ce paramtre contiendra videmment son PID mais, pour une application plus importante, il pourrait tre utile de sauvegarder sa valeur juste aprs le lancement en arrire-plan. Le script suivant contient deux branches dexcution distinctes, spares par un test if; la premire est celle du processus pre, et la seconde celle du ls. Chacun deux afche quelques informations sur leurs PID et appelle la commande sleep pour sendormir pendant une seconde.
pere_fils_1.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #! /bin/sh if [ "$MON_PID" != "$PPID" ] ; then # Processus Pre export MON_PID=$$ echo "PERE : mon PID est $$" echo "PERE : je lance un fils" $0 & sleep 1 echo "PERE : le PID de mon fils est $!" echo "PERE : je me termine" else # Processus FILS echo "FILS : mon PID est $$, celui de mon pre est $PPID" sleep 1 echo "FILS : je me termine" fi

Lexcution du script nest pas totalement dterministe, parfois le pre se termine en premier, dautres fois en second :
$ ./pere_fils_1.sh PERE : mon PID est 3098 PERE : je lance un fils FILS : mon PID est 3099, celui de mon pre est 3098 FILS : je me termine PERE : le PID de mon fils est 3099 PERE : je me termine $ ./pere_fils_1.sh PERE : mon PID est 3102 PERE : je lance un fils

Programmation shell avance CHAPITRE 6

157

FILS : mon PID est 3103, celui de mon pre est 3102 PERE : le PID de mon fils est 3103 PERE : je me termine $ FILS : je me termine

Nous pouvons observer que, lors de la seconde excution, le symbole dinvite $ du shell interactif est afch avant le dernier message du processus ls. Cela est d au fait que le shell interactif celui qui nous sert lancer le script fait dmarrer un processus, le pre, de PID 3102 en loccurrence, et attend son achvement pour afcher son symbole. Il ne sait rien de la prsence dun processus ls supplmentaire et, si ce dernier se termine aprs son pre, le shell interactif aura dj afch son symbole sur lcran. Pour rsoudre ce problme, il faut que le processus pre attende la n de son ls avant de se terminer son tour. La commande interne wait permet dattendre la n dun processus ls. Suivie dun numro de PID, elle attend spciquement la n du processus ls correspondant. Sans argument, elle attend la n de tous les processus ls existants. Dans le script suivant, nous avons ajout une petite boucle dans laquelle chaque processus compte jusqu 3 an de bien vrier les enchevtrements dexcution.
pere_fils_2.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #! /bin/sh function compte { local i; for i in $(seq 3) ; do echo "$1 : $i" sleep 1 done } if [ "$MON_PID" = "$PPID" ] ; then # Processus FILS echo "FILS : mon PID est $$, mon PPID est $PPID" compte "FILS" echo "FILS : je me termine" else # Processus Pre export MON_PID=$$ echo "PERE : mon PID est $$" echo "PERE : je lance un fils" $0 & echo "PERE : mon fils a le PID $!" compte "PERE" echo "PERE : j'attends la fin de mon fils" wait echo "PERE : je me termine"

158

Shells Linux et Unix par la pratique

Cette fois, quel que soit lordre de terminaison du comptage, le processus pre attend bien la n de son ls avant de sarrter.
$ ./pere_fils_2.sh PERE : mon PID est 3315 PERE : je lance un fils PERE : mon fils a le PID 3316 FILS : mon PID est 3316, mon PPID est 3315 PERE : 1 FILS : 1 FILS : 2 PERE : 2 PERE : 3 FILS : 3 PERE : j'attends la fin de mon fils FILS : je me termine PERE : je me termine $

Un processus ls doit souvent indiquer son pre les circonstances dans lesquelles il sest termin. Nous savons quun code de retour nul correspond, pour les tests if, une valeur vraie et un code non nul une valeur fausse. On peut toutefois afner la lecture de ce code de retour, car le paramtre spcial $? contient le code de la dernire commande termine en avant-plan. Lorsque lon sintresse au rsultat dun processus initialement lanc larrire-plan, la commande wait elle-mme renvoie ce code. Ainsi le processus ls du script suivant renvoie-t-il une valeur arbitraire, 14, qui est disponible dans lenvironnement de son pre une fois quil sest termin. On notera toutefois que cela ne fonctionne que si la commande wait est invoque en prcisant le PID du ls attendu. Dans le cas contraire, wait remplira toujours le paramtre $? avec une valeur nulle, et renverra galement zro.
pere_fils_3.sh: 1 2 3 4 5 6 7 8 9 10 11 #! /bin/sh if [ "$MON_PID" = "$PPID" ] ; then # Processus FILS echo "FILS : mon PID est $$, mon PPID est $PPID" echo "FILS : je me termine avec le code 14" exit 14 else # Processus Pre export MON_PID=$$ echo "PERE : mon PID est $$"

Programmation shell avance CHAPITRE 6

159

12 13 14 15 16 17 18

echo $0 & echo echo wait echo fi

"PERE : je lance un fils" "PERE : mon fils a le PID $!" "PERE : j'attends la fin de mon fils" $! "PERE : mon fils s'est termin avec le code $?"

Lexcution conrme bien lemploi du paramtre $?:


$ ./pere_fils_3.sh PERE : mon PID est 3953 PERE : je lance un fils PERE : mon fils a le PID 3954 FILS : mon PID est 3954, mon PPID est 3953 PERE : j'attends la fin de mon fils FILS : je me termine avec le code 14 PERE : mon fils s'est termin avec le code 14 $

Nous voyons que le shell propose les lments de programmation requis pour raliser des applications multitches performantes. Il ne faut pas croire que le lancement dun processus en arrire-plan dans un script, avec possibilit dattendre sa n et son code de retour, soit uniquement un cas dcole ; il sagit au contraire dun mcanisme trs intressant dans de nombreuses applications relles. Je citerai titre dexemple le cas dune application denregistrement de donnes horodates que jai mise au point pour un systme de scurit aroportuaire. Le logiciel denregistrement proprement dit est crit en langage C ; il tablit une connexion rseau avec un serveur dont le nom lui est fourni en argument, et copie les donnes reues dans un chier dont le nom lui est galement transmis en argument en ajoutant les informations dhorodatage requises. Ce logiciel fonctionne de manire continue, ne sarrtant que lorsquon lui envoie le signal SIGINT; ce moment, il ralise les dernires oprations ncessaires pour la mise jour de len-tte du chier, et se termine.
Les signaux sont traits un peu plus bas dans ce chapitre.

Pour faciliter larchivage des enregistrements, il avait t dcid de crer des chiers dune heure (qui taient en outre rpartis dans des rpertoires quotidiens, mais cela excde le cadre de notre propos). Aussi devais-je crire un script qui lance lenregistrement et larrte automatiquement une heure plus tard, pour en relancer un nouveau avec un nom diffrent. Pour programmer une commande excuter au bout dune heure, on utilise lutilitaire at, qui lance lexcution des commandes lues sur son entre standard linstant mentionn par largument en ligne de commande. Comme at lit toujours son

160

Shells Linux et Unix par la pratique

entre standard, on emploie un document en ligne pour lui transmettre les commandes souhaites. Voici le script qui fut nalement mis au point :
while true ; do FICHIER=$(date "+%Y_%m_%d_%H_%M") TEMP=/var/tmp/enreg_$$ /usr/bin/enregistreur --serveur 192.1.5.20 --fichier $TEMP & at now + 1 hours <<-FIN kill -INT $! FIN wait FICHIER=${FICHIER}$(date "+-%Y_%m_%d_%H_%M.dat") mv $TEMP $FICHIER done

Le script rel tait un peu plus compliqu, car il grait des conditions darrt gnral (dans le test du while) et le dplacement du chier vers le rpertoire adquat cr au besoin, mais dans lensemble le schma est le mme. Comme on souhaite obtenir des chiers aux noms signicatifs, on commence par crer une premire partie de nom avec la date de dbut. On cre aussi un nom de chier temporaire en utilisant le numro de PID du processus en cours. Lenregistreur est ensuite lanc en arrire-plan, puis une commande kill est programme pour lheure suivante, qui lui enverra le signal requis. Le script passe alors en attente de la n de lenregistrement. Le reste de la boucle est donc excut au bout dune heure. On construit la seconde partie du nom de chier, que lon utilise alors pour renommer le chier temporaire.

Arrire-plan et dmons
Le lancement dun processus en arrire-plan est on ne peut plus facile, puisquil suft de rajouter un & la ligne de commande lors de linvocation du programme. Pourtant, il est souvent ncessaire de prendre dautres mesures pour assurer un bon fonctionnement du logiciel. La plupart des shells, lorsquils reoivent un signal SIGHUP, le retransmettent tous les processus ls quils ont crs. Ce signal, dont la signication est hang up ( raccrochage , au sens tlphonique du terme), est notamment mis par les terminaux et les modems lors dune rupture de connexion. Certains shells mettent galement ce signal, systmatiquement ou sous contrle dune option, lorsquils se terminent. Quand aucune mesure particulire nest prise au sein du processus, la rception de ce signal le contraindra se terminer immdiatement. Cest gnant si on souhaite lancer un travail en tche de fond pour une longue dure, puis se dconnecter et rcuprer le rsultat le lendemain matin par exemple. Pour pallier ce problme, on peut recourir un utilitaire nomm nohup qui fait en sorte quun processus ne soit pas sensible au signal SIGHUP.

Programmation shell avance CHAPITRE 6

161

Cet utilitaire excute lapplication demande avec une priorit lgrement diminue, puisquil sagit dune tche de fond qui ne doit pas perturber les autres processus. Enn, il redirige la sortie standard et celle derreur vers le chier nohup.out ou ~/nohup.out, car le processus sexcute sans terminal pour afcher ses rsultats. Lemploi de nohup est trs simple ; il suft de lutiliser en prxe pour lancer le programme. Par exemple, le script suivant compte jusqu quatre en crivant son rsultat sur la sortie standard et en respectant une petite pause entre chaque criture.
comptage.sh: 1 2 3 4 5 6 #! /bin/sh for i in $(seq 1 4) ; do echo $i sleep 10 done

Excutons-le avec et sans nohup:


$ ./comptage.sh & [1] 17463 $ 1 2 3 4 [1]+ Done ./comptage.sh $ nohup ./comptage.sh & [1] 17472 nohup: appending output to `nohup.out' $ (attente 40 secondes, puis Entre) [1]+ Done $ cat nohup.out 1 2 3 4 $ nohup ./comptage.sh

Il convient de noter que, suivant la conguration du terminal (et plus particulirement de loption tostop de stty), un processus en arrire-plan peut tre arrt automatiquement sil essaie dcrire sur sa sortie standard.

162

Shells Linux et Unix par la pratique

Lorsquune application doit fonctionner longuement en arrire-plan et plus particulirement si elle doit offrir des services dautres processus, il devient intressant dessayer de la programmer comme un dmon Unix. Ce type de processus doit remplir un certain nombre de critres qui lui permettent ainsi de fonctionner dune manire sre et able aussi longtemps quil le requiert. Tout dabord, un dmon ne doit pas avoir de terminal de contrle. On peut observer cela avec la commande ps qui afche, dans la colonne TTY, un point dinterrogation pour les processus sans terminaux de contrle. Pour obtenir ce rsultat, on demande la cration dune nouvelle session de processus. Sans entrer dans des dtails un peu complexes, indiquons simplement quil faut utiliser la commande setsid, et, comme le processus ne doit pas tre leader de son groupe, quil doit sagir dun processus ls de celui qui est lavant-plan. Nous utiliserons donc un script qui, comme au paragraphe prcdent, se dupliquera pour que le processus pre se termine aprs avoir cr un ls. Le deuxime point important, cest de bien refermer les canaux dentre-sortie standards qui ont t mis en place par le shell lors du lancement du programme. Pour ce faire, on procde avec les oprateurs de redirection <&- et >&- qui indiquent la fermeture du ux. Nous ajouterons donc 0<&-, 1>&- et 2>&- sur la ligne de commande de lancement du dmon. Un dernier critre tablit enn quun dmon ne doit en aucun cas bloquer une partition que ladministrateur systme pourrait avoir besoin de dmonter. Lune des premires instructions sera donc cd/ qui dplace le rpertoire de travail vers la racine du systme de chiers. Toutefois, cela ne suft pas ; lorsque le noyau excute directement un script, il ouvre un descripteur, prcisment vers ce chier, quil emploie ensuite pour alimenter le shell. Le chier script lui-mme reste donc ouvert pendant toute la dure dexcution du programme, ce qui bloque la partition. La solution consiste lire le contenu du script dans une grande chane de caractres stocke dans une variable, et invoquer le shell en lui transmettant directement cette commande en argument de loption -c, an quil nait pas manipuler le chier. Nous allons crire un morceau de script qui pourra tre utilis pour lancer le reste du programme sous forme de dmon. Ce dernier naura ici aucun rle important, seulement attendre 30 secondes pour nous permettre quelques vrications de son comportement. Le processus pre utilisera le mcanisme syslog standard sous Linux pour communiquer le PID de son ls, conjointement un message sur sa sortie derreur standard.
demoniaque: 1 #! /bin/sh 2 3 if [ "$MON_PID" != "$PPID" ] ; then 4 export MON_PID=$$ 5 MON_LISTING=$(cat $0) 6 cd / 7 setsid /bin/bash -c "$MON_LISTING" "$0" "$@" 0<&- 1>&- 2>&- & 8 logger -t $(basename $0) "Le PID du demon est $!" 9 echo "Le PID du dmon est $!" >& 2

Programmation shell avance CHAPITRE 6

163

10 exit 0 11 fi 12 13 # Dbut du dmon proprement dit 14 15 sleep 30

Lexcution du programme permet effectivement quelques vrications :


$ ./demoniaque.sh Le PID du dmon est 17974 $ su Password: # tail -1 /var/log/messages dc 20 14:03:43 venux demoniaque.sh: Le PID du demon est 17974 # exit

Avec Linux, on peut sassurer, en consultant le pseudo-systme de chiers /proc, que le processus na aucun descripteur de chier ouvert :
$ ll /proc/17974/fd/ total 0

Puis, on vrie quil na pas de terminal de contrle, dans la colonne TTY de la commande
ps:
$ ps -l 17974 F S UID $ PID PPID C PRI NI ADDR 1 0 60 0 SZ WCHAN TTY TIME CMD 0:00 sleep 30 000 S 500 17974 - 286 nanosl ?

Ici, le dmon ne fait quattendre un peu avant de se terminer, mais en gnral il sagit dun programme utilitaire qui offre des services aux autres processus. Pour envoyer des requtes ou recevoir les rponses dun dmon, plusieurs mcanismes sont proposs par Unix. Nous allons examiner tout dabord la gestion des signaux, puis des mthodes de communication entre processus.

Signaux
On peut se reprsenter un signal comme une sorte dimpulsion quun processus envoie en direction dun autre. Les signaux sont surtout utiliss par le noyau pour indiquer un programme loccurrence dune situation extraordinaire (tentative daccs un emplacement mmoire invalide, instruction illgale, etc.), mais ils constituent aussi un moyen

164

Shells Linux et Unix par la pratique

dinteraction avec le terminal et lutilisateur (touches spciales comme Contrle-C, Contrle-Q, Contrle-S, Contrle-Z...). On utilise parfois les signaux pour implmenter un dialogue minimal entre processus.

Envoi dun signal


On procde lmission dun signal au moyen de la commande kill. On fait suivre la commande du numro du signal souhait, prcd dun tiret, puis du PID vis. Le signal est alors envoy au processus cible sous rserve que lmetteur dispose des autorisations ncessaires qui peut prendre des mesures en consquence. Le numro est souvent indiqu dune manire symbolique, avec les noms dcrits dans la table prsente plus bas. On peut ajouter un prxe SIG. Un script peut tre amen mettre quelques signaux principaux : SIGUSR1 et SIGUSR2 sont rservs aux applications utilisateur, et on les emploie souvent pour implmenter un dialogue entre des scripts personnels. SIGTERM, SIGINT, SIGQUIT, SIGKILL sont employs pour mettre n lexcution dun processus, ils sont prsents ici dans lordre croissant de leur caractre impratif. SIGHUP sert gnralement demander un dmon de relire ses chiers de conguration, ce qui vite de devoir larrter et de le relancer. Nous pourrons donc rencontrer dans les scripts des commandes dmission du genre :
kill -HUP $PID_DEMON kill -TERM $PID_SERVEUR kill -USR2 $PID_CLIENT

On notera quun PID ngatif correspond en fait lidentiant dun groupe de processus, ce qui permet denvoyer un signal tous les descendants dun processus. On y recourt rarement dans la programmation shell. Lemploi du PID -1 permet pour sa part de demander lenvoi dun signal tous les processus du systme. On nutilisera jamais cette commande en tant connect sous root pour viter darrter tout le systme mais, pour un utilisateur normal, eu gard aux permissions dmission, cest un moyen de tuer tous ses propres processus en une seule fois :
$ kill -KILL -1

Bien sr, on se retrouve dconnect, mais cest parfois la mthode la plus simple pour liminer un script qui se reproduit linni, comme :
#! /bin/sh $0 &

La gestion des signaux en programmation systme se rvle parfois assez complexe, surtout lorsquon dsire manipuler nement les possibilits quils offrent. Au niveau du shell, les fonctionnalits sont simplies, mais permettent dj une gestion assez rigoureuse des signaux.

Programmation shell avance CHAPITRE 6

165

Rception dun signal


Lorsquun processus reoit un signal, il adopte automatiquement lun des trois comportements suivants : Ignorer le signal : le processus continue simplement son travail, comme si de rien ntait. Capturer le signal : le droulement du processus est interrompu et une srie dinstructions prprogrammes est excute. Une fois ces instructions termines, le processus reprend son cours normal. Deux signaux ne peuvent pas tre capturs (et ne peuvent pas non plus tre ignors) : SIGKILL et SIGSTOP. Agir par dfaut : chaque signal est attribue une action par dfaut. Certains, comme SIGCHLD, nont aucune inuence sur le processus. Dautres, comme SIGTSTP, arrtent temporairement le processus. Enn la majorit dentre eux provoque la n du processus avec, comme SIGSEGV, cration dun chier core contenant une reprsentation de lespace mmoire, an de permettre le dbogage post mortem, ou, comme SIGINT, sans cration de ce chier. Pour congurer le comportement souhait pour un processus, on utilise la commande
trap. Celle-ci prend en argument une chane de caractres suivie dun symbole de signal.

Si la chane est absente, le processus reprend le comportement par dfaut pour le signal mentionn. Si la chane est vide, le signal sera ignor. Sinon, la chane sera value la rception du signal. En gnral, cette chane contiendra simplement le nom dune fonction charge de grer loccurrence du signal. Cette fonction est traditionnellement appele gestionnaire du signal (signal handler). Dans le script suivant, on va vrier que la chane transmise trap nest bien value qu la rception du signal, en y plaant une variable dont on modie le contenu. Le processus senvoie lui-mme un signal laide du paramtre $$ qui contient son propre PID.
exemple_trap.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #! /bin/sh function gestionnaire_1 { echo " -> SIGINT reu dans gestionnaire 1" } function gestionnaire_2 { echo " -> SIGINT reu dans gestionnaire 2" } trap '$GEST' INT echo "GEST non remplie : envoi de SIGINT" kill -INT $$ echo "GEST=gestionnaire_1 : envoi de SIGINT"

166

Shells Linux et Unix par la pratique

19 20 21 22 23 24

GEST=gestionnaire_1 kill -INT $$ echo "GEST=gestionnaire_2 : envoi de SIGINT" GEST=gestionnaire_2 kill -INT $$

Nous vrions que, tant que GEST nest pas remplie, la chane transmise trap est vide et le signal ignor, puis quau gr des modications de cette variable, le processus excute lun ou lautre des gestionnaires.
$ ./exemple_trap.sh GEST non remplie : envoi de SIGINT GEST=gestionnaire_1 : envoi de SIGINT -> SIGINT reu dans gestionnaire 1 GEST=gestionnaire_2 : envoi de SIGINT -> SIGINT reu dans gestionnaire 2 $

Notez galement que lutilitaire nohup, que nous avons voqu plus haut, est parfois implment par un script qui invoque :
trap "" HUP

avant dappeler la commande mentionne en argument, ce an que cette dernire ignore le signal SIGHUP.

Attente de signaux
Lorsquon emploie des signaux des ns de dialogue entre processus, il est important de bien vrier que la synchronisation entre les processus ne risque pas daboutir des situations de blocage, o chacun attend un message de son interlocuteur avant de poursuivre son excution. En rgle gnrale, il est recommand de restreindre laction du gestionnaire de signal la modication dune variable globale. Celle-ci sera consulte rgulirement dans le corps du script pour savoir si un signal est arriv. Par exemple, on pourra employer quelque chose comme :
USR1_recu=0; function gestionnaire_USR1 { USR1_recu=1; } trap gestionnaire_USR1 USR1

Et dans le corps du script :


# attente du signal while [ $USR1_recu -eq 0 ] ; do sleep 1 done # le signal est arriv, on continue...

Programmation shell avance CHAPITRE 6

167

Communication entre processus


La communication entre processus recouvre un large domaine de la programmation systme, o lon retrouve aussi bien les tubes ou les sockets rseau que les segments de mmoire partage ou les smaphores. Au niveau des scripts shell, la communication entre des processus distincts est toutefois assez restreinte. Nous avons dj voqu les possibilits de synchronisation autour des signaux USR1 et USR2, mais il sagit vraiment dune communication minimale. De plus, le processus metteur dun signal doit connatre son correspondant par son identiant PID. En dautres termes, sils ne sont pas tous les deux issus dun mme script (avec un mcanisme pre/ls comme nous lavons vu), il faut mettre au point une autre mthode de communication du PID (chier, par exemple). Les systmes Unix proposent toutefois un mcanisme tonnamment simple et puissant, qui permet de faire transiter autant dinformation quon le dsire entre des processus, sans liens pralables : les les Fifo (First In First Out, premier arriv, premier servi). Une le est une sorte de tunnel que le noyau met la disposition des processus ; chacun peut y crire ou y lire des donnes. Une information envoye lentre dune le est immdiatement disponible sa sortie, sauf si dautres donnes sont dj prsentes, en attente dtre lues. Pour que laccs aux les soit le plus simple possible, le noyau fournit une interface semblable aux chiers. Ainsi un processus crira-t-il dans une le de la mme manire quil crit dans un chier (par exemple avec echo et une redirection pour un script shell) et pourra-t-il y lire avec les mmes mthodes que pour la lecture dun chier (read par exemple). En outre, linterface tant identique celle des chiers, les autorisations daccs seront directement gres laide du propritaire et du groupe de la le. La cration dune le dans le systme de chiers sobtient avec lutilitaire mkfifo. Celuici prend en argument le nom de la le crer, ventuellement prcd dune option -m suivie du mode daccs en octal. Dans lexemple suivant, nous allons crer un systme client-serveur, dans lequel un processus serveur fonctionnant en mode dmon cre une le Fifo avec un nom bien dni. Notre serveur aura pour rle de renvoyer le nom complet qui correspond un identiant dutilisateur, en consultant le chier /etc/passwd. Les clients lui enverront donc dans sa le Fifo les identiants rechercher. Toutefois, pour que le serveur puisse rpondre au client, ce dernier devra galement crer sa propre le, et en transmettre le nom dans le message dinterrogation. Le script du client sera le plus simple. Il connat le nom de la le du serveur, ici ~/noms_ident.fifo, mais on pourrait convenir de dplacer ce chier vers un rpertoire systme comme /etc. Le script crera donc une le personnelle, et enverra lidentiant recherch, suivi du nom de sa le, dans celle du serveur. Il attendra ensuite la rponse et se terminera aprs avoir supprim sa le.
fifo_client.sh: 1 2 3 4 #! /bin/sh FIFO_SRV=~/noms_ident.fifo FIFO_CLT=~/fifo_$$.fifo

168

Shells Linux et Unix par la pratique

5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

if [ -z "$1" ] ; then echo "Syntaxe : $0 identifiant" >&2 exit 1 fi if [ ! -p $FIFO_SRV ] ; then echo "Le serveur n'est pas accessible" exit 1 fi mkfifo -m 0622 $FIFO_CLT if [ ! -p $FIFO_SRV ] ; then echo "Impossible de crer la file ~/fifo_$$.fifo" exit 1 fi echo "$1 $FIFO_CLT" > $FIFO_SRV cat < $FIFO_CLT rm -f $FIFO_CLT

Le serveur est un peu plus complexe : tout dabord, il doit basculer en arrire-plan, en mode dmon. Ensuite, pour tre certain de toujours dtruire la le Fifo quand il se termine, nous allons lui ajouter un gestionnaire pour les principaux signaux de terminaison, ainsi que pour le pseudo-signal EXIT qui est invoqu lorsque le script se termine normalement. Le serveur vrie quun autre serveur nest pas dj actif, sinon il lui envoie une demande de terminaison. Ensuite, il cre la le Fifo prvue et entre dans la boucle de fonctionnement principal. Cette boucle se droule jusqu ce quil reoive un identiant FIN. Lidentiant et le nom de la Fifo du client sont lus grce une instruction read. On balaye ensuite le chier /etc/passwd. Pour ce faire, le plus simple est de mettre en place une redirection depuis ce chier vers lentre standard grce exec. Cette mise en uvre a pour effet secondaire de ramener au dbut le pointeur de lecture dans le chier /etc/passwd. Pour sparer les champs qui se trouvent sur les lignes du chier, on recourt sans problme read, aprs avoir temporairement modi la variable IFS, pour quelle contienne le sparateur :. Si lidentiant est trouv, le nom complet est inscrit dans la le Fifo du client, et la boucle recommence.
fifo_serveur.sh: 1 2 3 4 5 6 7 8 9 #! /bin/sh # Passage en mode dmon if [ "$MON_PID" != "$PPID" ] ; then export MON_PID=$$ MON_LISTING=$(cat $0) cd / setsid /bin/bash -c "$MON_LISTING" "$0" "$@" 0<&- 1>&- 2>&- & logger -t $(basename $0) "Le PID du dmon est $!"

Programmation shell avance CHAPITRE 6

169

10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60

echo "Le PID du dmon est $!" >& 2 exit 0 fi FIFO_SRV=~/noms_ident.fifo function gestionnaire_signaux { rm -f $FIFO_SRV exit 0 } trap gestionnaire_signaux EXIT QUIT INT HUP if [ -e $FIFO_SRV ] ; then echo "FIN" > $FIFO_SRV & exit 0 fi mkfifo -m 0622 $FIFO_SRV if [ ! -p $FIFO_SRV ] ; then echo "Impossible de crer la file FIFO $FIFO_SRV" exit 1 fi FIN="" while [ ! $FIN ] ; do read IDENT FIFO_CLT < $FIFO_SRV TROUVE="" exec < /etc/passwd ANCIEN_IFS="$IFS" IFS=":" while read ident passe uid gid nom reste ; do if [ "$IDENT" == "$ident" ] ; then TROUVE="Oui" break fi done IFS=$ANCIEN_IFS if [ "$IDENT" == "FIN" ] ; then FIN="Oui" TROUVE="Oui" nom="Fin du serveur" fi if [ $TROUVE ] ; then echo "$nom" > $FIFO_CLT else echo "Non trouv" > $FIFO_CLT fi done

170

Shells Linux et Unix par la pratique

Lexcution conrme nos attentes :


$ ./fifo_serveur.sh Le PID du dmon est 6963 $ ./fifo_client.sh Syntaxe : ./fifo_client.sh identifiant $ ./fifo_client.sh cpb Christophe Blaess $ ./fifo_client.sh root root $ ./fifo_client.sh ftp FTP User $ ./fifo_client.sh inexistant Non trouv $ ./fifo_client.sh FIN Fin du serveur $ ./fifo_client.sh root Le serveur n'est pas accessible $ ls ~/*.fifo ls: Aucun fichier ou rpertoire de ce type $

La communication entre processus au moyen des les Fifo est un mcanisme puissant, mais il faut bien prendre garde aux risques dinteractions bloquantes si chacun des deux processus attend laction de lautre. Il est ais et amusant de tester les diffrentes situations en crant manuellement une le avec la commande mkfifo, puis dexaminer les comportements et les blocages en crivant dans la le avec echo"...">fifo ou en lisant avec cat<fifo depuis plusieurs fentres xterm diffrentes.

Entres-sorties
La programmation de scripts shell est bien souvent considre comme une sorte de plomberie visant relier des utilitaires qui prennent des donnes sur leur entre standard et crivent un rsultat sur la sortie standard, en fonction darguments rencontrs sur la ligne de commande. Ce schma est parfois un peu rducteur, et les mcanismes dentre-sortie proposs aux scripts peuvent tre plus complexes. Rappelons en premier lieu quil est possible de mettre en place ou de modier les redirections au sein mme dun script. On peut employer un regroupement de commandes, comme cela a t vu au chapitre 4, et modier temporairement les entre et sortie standards uniquement pour cette portion. On peut galement modier les redirections grce

Programmation shell avance CHAPITRE 6

171

la commande exec, sans argument, comme cela a t fait dans le script fifo_serveur du paragraphe prcdent. Il est donc possible en faisant souvent preuve de patience et de bonne volont de raliser des scripts shell susceptibles de manipuler intelligemment des chiers de donnes, sans recourir pour autant une programmation dans un autre langage. Lorsquon rdige des chanes de commandes qui agissent comme des ltres (entre standard vers sortie standard), deux besoins apparaissent souvent : observer, durant la phase de mise au point, les donnes qui circulent en un point quelconque du pipeline ; regrouper des informations qui se trouvent sur des lignes successives pour construire une seule commande. Deux utilitaires permettent de raliser ces fonctions : tee pour la premire, et xargs pour la seconde. tant donn leur utilit pour la rdaction de scripts corrects, il est intressant de sattarder quelques instants sur leur fonctionnement.

tee
Cet utilitaire peut simaginer comme un T de plomberie, do son nom, cest--dire un accessoire que lon insre dans un pipeline pour disposer dune sortie annexe. tee lit les donnes sur son entre standard et les copie sur sa sortie standard, tout en envoyant une copie supplmentaire dans tous les chiers dont le nom lui est fourni en argument. Cette commande permet donc denregistrer le trac de donnes entre les diffrentes commandes dun pipeline, ce qui est trs apprciable lors de la mise au point. Il est galement possible dutiliser le nom de chier spcial /dev/tty pour obtenir un afchage lcran des donnes qui transitent par tee. Cest trs utile lorsque lapplication suivante est un processus vorace qui consomme toutes les donnes sans que nous ayons de retour dinformations immdiat sur son excution. En voici un exemple sous Linux, avec une coute sur linterface ethernet eth0, que lon ltre par le nom dhte :
$ su Password: # tcpdump -i eth0 -l | grep 192.1.1.51 > capture Kernel filter, protocol ALL, datagram packet socket tcpdump: listening on eth0 (Contrle-C) 485 packets received by filter # ls -l capture -rw-r--r-# 1 root root 0 Mar 15 16:10 capture

172

Shells Linux et Unix par la pratique

Le chier de capture est vide, aucune information nest donc parvenue en n de chane ; ne sachant pas o se situe le problme, nous pouvons insrer un tee avant la dernire commande :
# tcpdump -i eth0 -l | tee /dev/tty | grep 192.1.1.51 > capture Kernel filter, protocol ALL, datagram packet socket tcpdump: listening on eth0 16:15:54.607707 < tracy.1044 > venux.telnet: . 106236379:106236379(0) ack 392332 3076 win 7660 (DF) 16:15:54.607794 > venux.telnet > tracy.1044: P 1:82(81) ack 0 win 32120 (DF) 16:15:54.827418 < tracy.1044 > venux.telnet: . 0:0(0) ack 82 win 7579 (DF) 16:15:54.827505 > venux.telnet > tracy.1044: P 82:260(178) ack 0 win 32120 (DF) 16:15:55.047058 < tracy.1044 > venux.telnet: . 0:0(0) ack 260 win 7401 (DF) 16:15:55.047138 > venux.telnet > tracy.1044: P 260:417(157) ack 0 win 32120 (DF) 16:15:55.266746 < tracy.1044 > venux.telnet: . 0:0(0) ack 417 win 8760 (DF) 16:15:55.266824 > venux.telnet > tracy.1044: P 417:576(159) ack 0 win 32120 (DF) (Contrle-C) 8 packets received by filter # ls -l capture -rw-r--r-# 1 root root 0 Mar 15 16:15 capture

Nous voyons quen loccurrence, lerreur consistait attendre une adresse IP numrique, alors que le processus tcpdump afchait les noms dhte sous forme symbolique. Avoir le rexe dutiliser tee pour sonder le passage des donnes dans un pipeline permet souvent de gagner du temps lors de la mise au point de commandes composes qui sont complexes.

xargs
La commande xargs, hlas trop souvent mconnue, est indispensable pour raliser certaines tches. Elle prend des lignes de texte sur son entre standard, les regroupe pour les transmettre en argument avant de lancer une autre commande. Cest le seul moyen efcace de transformer des donnes arrivant dans un ux du type pipeline en informations sur une ligne de commande. Son utilisation la plus frquente est lassociation des outils find et grep pour rechercher des chanes de caractres, en parcourant rcursivement les rpertoires qui contiennent les chiers. Le principe consiste effectuer la descente des rpertoires laide de find, qui nous afchera la liste des chiers rencontrs sur sa sortie standard. Par exemple :

Programmation shell avance CHAPITRE 6

173

$ cd /usr/src/linux $ find . -name '*.c' ./Documentation/networking/ip_masq/ip_masq-API-ex.c ./arch/i386/boot/compressed/misc.c ./arch/i386/boot/tools/build.c ./arch/i386/kernel/apm.c ./arch/i386/kernel/bios32.c ./arch/i386/kernel/i386_ksyms.c ./arch/i386/kernel/init_task.c ./arch/i386/kernel/io_apic.c ./arch/i386/kernel/ioport.c ./arch/i386/kernel/irq.c ./arch/i386/kernel/ldt.c ./arch/i386/kernel/mca.c ./arch/i386/kernel/mtrr.c ./arch/i386/kernel/process.c ./arch/i386/kernel/ptrace.c ./arch/i386/kernel/setup.c ./arch/i386/kernel/signal.c ./arch/i386/kernel/smp.c ./arch/i386/kernel/sys_i386.c ./arch/i386/kernel/time.c ./arch/i386/kernel/traps.c ./arch/i386/kernel/visws_apic.c ./arch/i386/kernel/vm86.c ./arch/i386/lib/delay.c [ ] $

Nous souhaitons pouvoir examiner chacun de ces chiers avec grep. La meilleure solution consiste utiliser xargs pour ajouter tous ces noms la suite de la ligne de commande. Ainsi, on emploiera :
$ find . -name '*.c' | xargs grep ipv6_getsockopt ./net/ipv6/ipv6_sockglue.c:int ipv6_getsockopt(struct sock *sk, int level, int optname, char *optval, ./net/ipv6/raw.c: return ipv6_getsockopt(sk, level, optname, optval, ./net/ipv6/tcp_ipv6.c: ipv6_getsockopt, ./net/ipv6/tcp_ipv6.c: ipv6_getsockopt, ./net/ipv6/udp.c: ipv6_getsockopt, /* getsockopt */ $

174

Shells Linux et Unix par la pratique

La lecture de la documentation de xargs est intressante, car il sagit dun utilitaire souvent salvateur quand il sagit de transmettre le rsultat dune application sur la ligne de commande dune autre. Citons, par exemple, le lancement dun diteur sur tous les chiers qui contiennent une chane donne (loption -l de grep demande que ne soient afchs que les noms des chiers o la chane se trouve) :
$ grep -l interface_eth '*.pm' | xargs nedit

Interface utilisateur
Nous avons dj voqu, dans le chapitre 4, quelques fonctions qui permettent dassurer une interaction simple avec lutilisateur (echo, read, select). Il est toutefois possible daller un peu plus loin, grce quelques utilitaires auxquels on peut avoir recours pour manipuler les caractristiques du terminal.

stty
Lutilitaire stty permet de modier de nombreux paramtres du terminal employ par lutilisateur. lorigine, les terminaux Unix taient relis lunit centrale par des liaisons srie, et une grande partie du paramtrage concerne ces lignes de communication. De nos jours, les terminaux sont essentiellement des fentres xterm ou des consoles virtuelles, et la conguration srie ne nous concerne plus. Il est nanmoins possible de modier le comportement du pilote du terminal, de manire raliser des saisies dynamiques. Les programmeurs qui dbutent sous Unix sen soucient ds quils remarquent que les routines de saisie de caractres, que ce soit en C, en Pascal, ou sous shell, ncessitent une pression de la touche Entre pour valider la saisie. En ralit, ce comportement nest pas d aux fonctions dentre proprement dites, mais uniquement au paramtrage du terminal. Lutilitaire stty comporte de nombreuses options, mais nous en retiendrons seulement quelques-unes :
Option Signication
Afcher la conguration en cours dans un format qui puisse tre utilis pour la restaurer ultrieurement. Afcher la conguration de manire intelligible. Remise du terminal dans un tat utilisable. Supprimer lcho des caractres saisis. Basculer en mode non canonique, ce qui modie le comportement vis--vis de certaines touches spciales. En mode non canonique, faire chouer la lecture si m caractres nont pas t lus au bout de t diximes de secondes.

-g -a sane -echo -icanon min m time t

Programmation shell avance CHAPITRE 6

175

On retiendra que la lecture au vol de caractres se fait en congurant le terminal avec :


stty -icanon min 0 time 1

et en employant ensuite read, comme dans le script suivant :


saisie_touche.sh: 1 2 3 4 5 6 7 8 9 10 11 12 #! /bin/sh sauvegarde_stty=$(stty -g) stty -icanon time 1 min 0 -echo touche="" while [ -z "$touche" ] ; do read touche done echo "Touche presse = " $touche stty $sauvegarde_stty

Lexcution est surtout intressante observer en situation relle, en raison de son aspect dynamique.
$ ./saisie_touche.sh (pression sur a) Touche presse = a $

On peut utiliser time 0, auquel cas la lecture choue immdiatement, mais je prfre employer un temps dattente minimal dun dixime de seconde. La diffrence nest pas vraiment sensible pour lutilisateur et on vite de laisser la boucle consommer inutilement des cycles CPU durant lattente.

tput
Comme les systmes Unix acceptent la connexion de trs nombreux types de terminaux diffrents, la gestion de leurs diffrentes proprits est vite devenue problmatique. Aussi diverses mthodes ont-elles t mises au point pour sabstraire des diffrences matrielles et offrir aux applications des fonctionnalits de haut niveau (par exemple, effacer lcran , placer le curseur au point X Y , etc.). On dispose dune bibliothque nomme ncurses, qui utilise les descriptions rencontres dans la base de donnes terminfo pour grer les diffrents terminaux. Au niveau du shell, un utilitaire particulier, nomm tput, permet daccder ces possibilits. On peut lui transmettre des ordres quil va interprter en fonction des commandes qui sont indiques dans la base de donnes terminfo.

176

Shells Linux et Unix par la pratique

On peut consulter la page de manuel de terminfo pour dcouvrir les diffrentes commandes supportes. De manire portable, seules les commandes clear (effacement de lcran) et reset (rinitialisation du terminal) sont vraiment sres. Toutefois la commande cup, suivie de deux nombres L et C, place sur de nombreux Unix le curseur en ligne L et colonne C de lcran.

dialog
Il existe sous Linux un utilitaire trs sympathique pour raliser des interfaces utilisateur conviviales en mode texte : dialog. Cet outil permet de programmer trs simplement des menus de choix, des botes de saisie, des botes de dialogue avec des boutons de rponse, des jauges indiquant la progression dun programme, etc. Comme ce dernier lment le suggre, le projet dialog tait lorigine destin simplier lcriture de scripts dinstallation. Il est dailleurs largement utilis par certaines distributions Linux. La portabilit des scripts utilisant dialog est un peu amoindrie, mais il faut savoir que ce produit sappuie uniquement sur linterface ncurses dcrite ci-dessus, et devrait donc pouvoir tre recompil et install sur nimporte quel systme Unix. La documentation de dialog est sufsamment claire pour que lutilisateur intress sy reporte aisment.

Dboguer un script
Le dbogage dun script shell est une tche souvent indispensable, mais pas si simple que lon pourrait le croire au premier abord, en raison des imbrications entre shell, sous-shell, excution des commandes dans des sous-processus, etc. cela vient sajouter un manque doutils de suivi dynamique de lexcution. On peut nanmoins proposer quelques mthodes dexamen dun script bogu. Tout dabord, pour essayer de lutter contre les fautes de frappe, on emploiera en dbut de script loption shell setu, ou setonounset, qui dclenche une erreur si on fait rfrence une variable non dnie. Ainsi le script suivant :
erreur_unset.sh: 1 2 3 4 5 6 7 #! /bin/sh set -o nounset if [ -z "$Chaine" ] ; then echo "La chane est vide" fi

dclenche-t-il cette erreur :


$ ./erreur_unset.sh ./erreur_unset.sh: Chaine: unbound variable $

Programmation shell avance CHAPITRE 6

177

alors que son excution sans la ligne 3 donne :


$ ./erreur_unset.sh La chane est vide $

Cette option rgle dj un nombre tonnamment important de problmes. On peut aller plus loin avec loption setv, ou setoverbose, qui permet dafcher les lignes au fur et mesure de leur excution. Toutefois, lafchage se faisant par blocs de commande complets, on ne sait pas toujours exactement quel endroit se trouve le point dexcution courant. Une option plus utile pour suivre plus exactement le droulement du script est setx ou
setoxtrace. En effet, elle indique le suivi pour chaque commande simple.

Lorsquon souhaite suivre lvolution dune variable au cours dun script, on peut avec certains shells utiliser un gestionnaire sur le pseudo-signal DEBUG. Celui-ci sera invoqu aprs lexcution de chaque commande simple. On peut y afcher par exemple le contenu de la variable spciale LINENO qui contient le numro de la dernire ligne excute. On peut galement y observer le contenu de toute autre variable globale. En ce qui concerne le compte rendu des informations de suivi, on peut utiliser naturellement echo, redirig vers un chier (avec loprateur >> pour mettre les traces la suite les unes des autres), ou la commande logger qui envoie un message au systme de journalisation syslog. La commande ulimit permet de restreindre la consommation des ressources systme par un processus. On se reportera la page de manuel du shell pour connatre les limitations possibles ; par exemple, sous Linux, il peut tre intressant dinterdire la cration dun chier core (ulimit-c0), de restreindre le nombre de processus simultans pour le mme utilisateur (ulimit-u64) ou de limiter la consommation CPU pour empcher un processus en boucle de tourner trop longtemps (ulimit-t4). Au moyen de la commande interne times, on peut obtenir des statistiques sur les temps utilisateur et systme consomms par un processus et par ses descendants. Cela permet parfois dafner la mise au point en recherchant les portions dun programme les plus gourmandes en temps processeur.

Virgule ottante
Bash ne permet pas deffectuer de calculs en virgule ottante. Certaines version de Ksh le permettent, mais la portabilit est amoindrie. Toutefois, les scripts shell peuvent quand mme bncier de larithmtique des nombres rels grce un outil standard nomm bc. Cet outil peut tre utilis de manire interactive (comme une calculatrice) ou dans un script.

178

Shells Linux et Unix par la pratique

Dans la bibliothque tendue de bc (accessible via son option l en ligne de commande), la fonction a(x) renvoie larc tangente de x. Voici un exemple interactif :
$ bc -l 4 * a(1) 3.14159265358979323844 quit $

Et voici un moyen dincorporer un appel bc dans un script shell :


appel_bc.sh: 1 2 3 4 5 6 #! /bin/sh X=1.234 Y=5.6789 Z=$( echo "$X * $Y" | bc -l) echo "Z vaut : $Z"

Ce qui nous donne lexcution :


$ ./appel_bc.sh Z vaut : 7.0077626 $

Conclusion
Ce chapitre sur la programmation avance avec le shell nous a permis de constater que les scripts dvelopps pour ce shell nont rien de triviaux ; ils peuvent en effet tenir des rles complexes, aussi bien du point de vue des fonctionnalits systme, telles que la programmation des dmons ou les communications entre processus, que sous laspect dinterface utilisateur complte.

7
Expressions rgulires Grep
Les expressions rgulires se rvlent un support essentiel pour un bon nombre dutilitaires Unix. Elles sont la base de loutil grep, que nous allons tudier dans ce chapitre, mais elles servent aussi de fondement pour les langages Sed et Awk que nous verrons dans les chapitres venir. Le terme d expression rgulire traduction littrale de langlais regular expression est largement plus rpandu que la traduction plus exacte expression rationnelle . Jutiliserai les deux termes dans ce chapitre. Nous allons commencer par tudier la structure des expressions rationnelles simples et tendues avec loutil grep, an de faciliter le travail ultrieur avec Sed, Awk ou Perl.

Introduction
Une expression rgulire est une description dune chane de caractres. Elle se prsente elle-mme sous forme de chane, mais son contenu est symbolique et doit tre interprt comme tel. Par exemple, lexpression ^[[:digit:]]+ -[[:blank:]]+.*$ est une description qui dit en substance : au moins un chiffre en dbut de chane ; suivi dun espace ; suivi dun tiret ; suivi dun ou plusieurs espaces ou tabulations ; suivis dun nombre quelconque de caractres terminant la chane. Au vu de cette chane de caractres, on devine quil est parfois plus facile dcrire une expression rationnelle partir dune description en clair que de comprendre ce que signie une expression dj crite

180

Shells Linux et Unix par la pratique

On dit quune chane correspond une expression lorsque son contenu est dcrit par cette dernire. Une expression rationnelle sert gnralement slectionner une ou plusieurs chanes dans un ensemble donn, par exemple des lignes spciques dans un chier. Une expression rationnelle peut donc, la plupart du temps, tre mise en correspondance avec plusieurs chanes diffrentes, et parfois mme une innit de chanes. Dans ce chapitre, nous nous intresserons essentiellement la mise en correspondance qui peut tre faite entre lexpression rationnelle et une portion dune chane. Bien sr, ltape suivante du traitement est la plus importante ; selon les applications, on peut afcher la chane complte (slection de lignes avec grep), supprimer ou modier la portion slectionne (scripts sed), ou lancer des actions qui dpendent de la structure de la chane (traitement de chiers avec awk).

Expressions rgulires simples


On dispose de deux types dexpressions rationnelles : les simples et les tendues. En pratique, la diffrence entre les deux se situera sur la ncessit ou non de prxer certains symboles spciaux par un backslash (barre oblique inverse). Nous allons examiner en premier lieu le cas des expressions rgulires simples. Ensuite, nous indiquerons les modications quil convient dapporter pour utiliser des expressions rgulires tendues. Une expression rationnelle est mise en correspondance caractre par caractre avec une chane. Certains caractres ont des signications spciales. Dans les expressions rationnelles simples, ces mtacaractres sont : . * \ [ ] ^ et $. Tous les autres caractres ne sont mis en correspondance quavec leurs propres valeurs. Ainsi lexpression rgulire abcd ne peut-elle correspondre qu la chane abcd. Pour tester la correspondance entre une expression rgulire et une ou plusieurs chanes de caractres, nous allons utiliser un petit script bas sur grep.
regexp.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #! /bin/sh EXPRESSION="$1" # Eliminons l'expression des arguments de ligne de commande : shift # Puis comparons-la avec les chanes : for chaine in "$@" do echo "$chaine" | grep "$EXPRESSION" > /dev/null if [ $? -eq 0 ] then echo "$chaine : OUI" else echo "$chaine : NON" fi done

Expressions rgulires Grep CHAPITRE 7

181

Voici un exemple dexcution :


$ ./regexp.sh ABCD ABCD abcd ABCD : OUI abcd : NON $

On notera que notre programme afche une russite si lexpression rationnelle peut tre mise en correspondance avec une sous-partie de la chane propose, mme si la chane complte nest pas utilise :
$ ./regexp.sh ABCD xyzABCD123 xyzABCD123 : OUI $

Si on veut sassurer que toute la chane est employe, on utilisera les symboles de dbut et de n de chane, comme nous lexpliquerons plus avant.
Le symbole gnrique .

Le caractre point ., dans une expression, est un symbole gnrique qui peut reprsenter nimporte quel caractre dans la chane :
$ ./regexp.sh A.B AxB A.B AxyB AxB : OUI A.B : OUI AxyB : NON $

Ici, la chane a t rejete car le point correspond sans problme au x, mais le B de lexpression rationnelle ne peut pas sassocier au y. Si on veut introduire un point littral dans lexpression rationnelle, il faut le faire prcder dun backslash : \.
$ ./regexp.sh 'A\.B' AxB A.B AxB : NON A.B : OUI $

On notera lemploi des apostrophes pour encadrer lexpression rgulire, pour viter que le shell nintervienne et ne supprime le backslash avant dinvoquer le script.

182

Shells Linux et Unix par la pratique

Dbut et n de chanes

Les deux symboles spciaux, ^ et $, reprsentent respectivement le dbut et la n de la chane. Ils ne sont pas mis en correspondance avec un caractre vritable, mais avec une chane vide. Par exemple, ^a peut correspondre nimporte quelle chane dont le premier caractre est un a. De mme, a$ peut sappliquer toute chane qui se termine par un a. En corollaire, ^a$ reprsente uniquement la chane a.
$ ./regexp.sh '^a' abcd bacd abcd : OUI bacd : NON $ ./regexp.sh 'd$' abcd abdc abcd : OUI abdc : NON $ ./regexp.sh '^abc$' abcd xabc abc abcd : NON xabc : NON abc : OUI $

Lexpression ^$ reprsente la chane vide. Comme la plupart des logiciels qui traitent leurs entres ligne par ligne suppriment dans la chane reue le caractre de saut de ligne nal, une ligne blanche (dont la reprsentation physique est un caractre Ascii 0x0A) apparat souvent sous forme de chane vide. Un moyen mnmotechnique pour se souvenir des rles respectifs de ^ et $ consiste observer leur position sur un clavier de type Azerty. Le ^ se trouve gauche du $ et reprsente donc le dbut de la chane. Sur les claviers anglo-saxons lordre est invers ; pour une fois que les utilisateurs francophones sont favoriss, protons-en !
Figure 71

Dbut et n de chane sur un clavier Azerty

Expressions rgulires Grep CHAPITRE 7

183

Les caractres ^ ou $ nont de signication particulire que sils apparaissent en dbut ou en n dexpression rationnelle. Sinon, ils reprennent leur valeur littrale. Autrement dit, le symbole de dbut de chane reprend sa valeur originale ^ lorsquil apparat ailleurs quau dbut de lexpression. Attention toutefois ce comportement, il nest pas totalement portable sur tous les Unix. Pour sassurer quun $ ou un ^ reprsente le caractre littral concern, on le prxera par un backslash \. De mme, si on doit faire apparatre un symbole ^ littral en dbut dexpression ou un $ en n dexpression, on les fera toujours prcder dun backslash.
Alternatives

Nous avons vu quun caractre ordinaire dans lexpression rationnelle reprsente son quivalent dans la chane et que le point reprsente nimporte quel caractre. Le choix est pour linstant un peu radical : soit nous connaissons parfaitement le caractre recherch, soit nous les acceptons tous ! Par chance, il est possible dintroduire un peu de pondration dans notre mise en correspondance, en utilisant les alternatives et les classes de caractres. Le caractre |, lorsquil est prcd dun backslash, indique une alternative entre deux caractres. Par exemple :
$ ./regexp.sh 'Ax\|yB' AxB AyB AzB AxB : OUI AyB : OUI AzB : NON $

On peut associer plusieurs alternatives successives, bien que cela ne soit pas trs efcace.
$ ./regexp.sh 'Ax\|y\|zB' AxB AyB AzB AwB AxB : OUI AyB : OUI AzB : OUI AwB : NON $

Le choix du symbole \| pour reprsenter une alternative est a priori surprenant. En fait il sagit du symbole | des expressions rgulires tendues (que nous verrons plus bas), qui a t import dans les expressions simples, en utilisant le backslash pour lui ajouter cette signication.
Listes

Lorsque plusieurs caractres peuvent convenir un emplacement donn, nous avons vu quil est possible denchaner les alternatives, mais crire a\|b\|c\|d\|e\|f w\|x\|y\|z

184

Shells Linux et Unix par la pratique

nest pas trs pratique. Il est donc possible dindiquer des listes de caractres susceptibles de correspondre un caractre de la chane. On regroupe la liste entre crochets [ et ], et son contenu sera mis en correspondance avec un seul caractre. Par exemple :
$ ./regexp.sh 'A[xyz]B' AxB AyB AzB AwB AxB : OUI AyB : OUI AzB : OUI AwB : NON

On notera quau sein des crochets, les caractres spciaux .,* et \ reprennent leur signication littrale et nont pas tre prxs par un backslash. Pour insrer un crochet ouvrant dans une liste, il convient de le placer en dernire position. Symtriquement, un crochet fermant sera plac en premire position :
$ ./regexp.sh 'A[.*\]B' AxB A.B 'A*B' 'A\B' AxB : NON A.B : OUI A*B : OUI A\B : OUI $

Intervalles

Pour viter davoir indiquer trop de caractres successifs dans la liste, par exemple [abcdefghijklmnopqrstuvwxyz], on peut utiliser un intervalle. Pour ce faire, on spare les deux bornes par un tiret, et lintervalle acceptera tous les caractres intermdiaires du jeu Ascii :
$ ./regexp.sh 'A[a-z][0-9]B' Ac5B AC5B AczB Ac5B : OUI AC5B : NON AczB : NON $

Si le premier caractre aprs le crochet ouvrant est un accent ^, la signication de la liste est inverse ; elle pourra correspondre nimporte quel caractre, sauf ceux qui sont indiqus :
$ ./regexp.sh 'A[a-z]B' 'A#B' A8B AcB A#B : NON A8B : NON AcB : OUI

Expressions rgulires Grep CHAPITRE 7

185

$ ./regexp.sh 'A[^a-z]B' 'A#B' A8B AcB A#B : OUI A8B : OUI AcB : NON $

tout autre emplacement de la liste, laccent circonexe reprend sa valeur littrale. Le tiret reprend sa signication littrale lorsquil est plac en dernier ou en premier (ventuellement aprs un accent circonexe).
Classes

Lutilisation dintervalles nest pas trs portable, car cette faon de procder sappuie uniquement sur lordre des caractres dans le jeu Ascii, qui est loin dtre le seul, et qui ne contient pas les caractres accentus par exemple. Pour amliorer la portabilit des expressions rgulires, on peut recourir la notion de classes de caractres. Une classe permet de dnir la signication dun caractre. En outre, les classes varient en fonction de la localisation du systme. Nous verrons les diffrences essentielles entre l Ascii et lIso-8859-15 (Latin-15) utilis en Europe de lOuest. Le nom dune classe doit tre indiqu avec la notation [:nom:], et ce obligatoirement lintrieur dune liste entre crochets. On crit donc en gnral [[:nom:]]. Douze classes standards sont disponibles :
Nom Signication
Lettres alphabtiques dans la localisation en cours. Chiffres dcimaux. Chiffres hexadcimaux. Chiffres ou lettres alphabtiques. Lettres minuscules dans la localisation en cours. Lettres majuscules dans la localisation en cours. Caractres blancs. Caractres despacement. Signes de ponctuation. Symboles ayant une reprsentation graphique. Caractres imprimables (graph et lespace). Caractres de contrle.

Ascii

Iso-8859-15

alpha digit xdigit alnum lower upper blank space punct graph print cntrl

[A-Za-z] [0-9] [0-9A-Fa-f] [[:alpha:][:digit:]] [a-z] [A-Z]


espace et tabulation espace, tabulation, sauts de ligne et de page, retour chariot

[A-Za-z ] y
idem Ascii idem Ascii

[[:alpha:][:digit:]] [a-z ] y [A-ZY]


idem Ascii idem Ascii idem Ascii

[]!"#$%&'()*+,-./:;<=>?@\ ^_`{|}~[] [[:alnum:][:punct:]] [[:graph:]]


Codes Ascii infrieurs 31, et caractre de code 127

[[:alnum:][:punct:]] [[:graph:]]
idem Ascii

186

Shells Linux et Unix par la pratique

Voyons la diffrence de traitement des caractres accentus entre la localisation franaise (fr_FR) et la localisation par dfaut (vide).
$ unset LC_ALL $ unset LANG $ export LANG=fr_FR $ ./regexp.sh 'A[[:alpha:]]B' AaB AB AB AB AaB : OUI AB : OUI AB : OUI AB : OUI $ LANG= $ ./regexp.sh 'A[[:alpha:]]B' AaB AB AB AB AaB : OUI AB : NON AB : NON AB : NON $

Lutilisation des classes de caractres amliore sensiblement la lisibilit de lexpression rationnelle et la rend plus portable vers dautres environnements. Il existe deux autres types de classes de caractres dont nous ne traiterons pas en raison de leur trs faible utilisation : les symboles de juxtaposition et les classes dquivalences qui se prsentent respectivement sous la forme [.symboles.] et [=classe=] dans une liste entre crochets.
Rptitions

Jusqu prsent, nous avons mis la suite des expressions rationnelles qui dcrivent un caractre chacune, pour reprsenter tous les caractres de la liste. Si nous voulons dcrire un mot de quatre lettres entour de blancs, nous crirons :
[[:blank:]][[:alpha:]][[:alpha:]][[:alpha:]][[:alpha:]][[:blank:]]

Ce qui est non seulement peu lgant, mais manque aussi defcacit (comment faire si nous ne connaissons pas lavance le nombre de lettres ?). Il faut savoir que lon peut placer la suite de la description dun caractre des oprateurs de rptition. Loprateur le plus simple est lastrisque * qui signie zro, une, ou plusieurs occurrences de llment prcdent . Par exemple, la chane ab*c peut correspondre : ac: lastrisque autorise labsence de llment (zro occurrence) ; abc: une seule occurrence, pas de rptition ; abbc: une rptition ; abbbbbbbbbbbbc: autant de rptitions que lon veut.

Expressions rgulires Grep CHAPITRE 7

187

Lorsque lastrisque est plac suite dun point, il remplacera autant de caractres quil le faudra. La mise en correspondance est gloutonne ; lexpression rationnelle avalera le plus possible de caractres. Par exemple, si on propose la chane abbcbbbbcbbbbbbc pour lexpression rationnelle a.*c, la mise en correspondance ira jusqu la n de la chane. Naturellement, si lexpression tait ab*c, la correspondance sarrterait au premier c. Lastrisque reprend sa valeur littrale lorsquil est prcd dun backslash, ou lorsquil se trouve dans une liste entre crochets. Si une chane est compose de mots spars par des espaces ou des tabulations, on peut par exemple trouver le premier mot avec lexpression suivante :
^[[:blank:]]*[[:alpha:]][[:alpha:]]*[[:blank:]]*

Nous voulons avoir au moins une lettre, aussi avons-nous rpt deux fois lexpression en rendant seulement la seconde facultative et rptable. Il existe toutefois une manire plus simple de procder, laide de loprateur \+ qui signie une ou plusieurs occurrences de llment prcdent . Cette fois-ci, la chane ac ne peut pas correspondre lexpression rationnelle ab\+c, car le b doit tre prsent au moins une fois :
$ ./regexp.sh 'ab\+c' ac abc abbbc ac : NON abc : OUI abbbc : OUI $

Cela nous permet de rcrire ainsi lexpression dcrivant le premier mot dune chane :
^[[:blank:]]*[[:alpha:]]\+[[:blank:]]*

Loprateur complmentaire, \?, accepte zro ou une occurrence de llment prcdent :


$ ./regexp.sh 'ab\?c' ac abc abbbc ac : OUI abc : OUI abbbc : NON $

Enn, loprateur de rptition qui a la forme \{n,m\} signie au moins n et au plus m occurrences de llment prcdent :
$ ./regexp.sh 'ab\{4,6\}c' abbc abbbbc abbbbbbc abbbbbbbc abbc : NON abbbbc : OUI abbbbbbc : OUI abbbbbbbc : NON $

188

Shells Linux et Unix par la pratique

Plusieurs variantes de cet oprateur sont disponibles : \{n,\}: au moins n occurrences ; \{0,m\}: au plus m occurrences ; \{n\}: exactement n occurrences. On doit prciser, bien que cela soit logique, quun oprateur de rptition qui se trouve la suite du caractre gnrique ., ou dune liste de caractres, ne demande pas la rptition exacte du mme caractre, mais implique une squence de caractres correspondant au choix offert par la liste ou le symbole gnrique :
$ ./regexp.sh 'A.\{3\}C' AxyzC AxyzC : OUI $

Groupements

Sil est pratique de pouvoir indiquer une rptition de caractres, on peut aussi tre amen rechercher des rptitions de squences de caractres, ou de sous-expressions rationnelles. On peut ainsi dnir des groupements de caractres laide des symboles \ ( et \). Lorsquun oprateur de rptition est plac la suite dun groupement, il agit sur lensemble de la squence. Par exemple, lexpression rationnelle \(123\)\{2\} demande deux rptitions de la chane 123:
$ ./regexp.sh 'A\(123\)\{2\}B' A123B A123123B A123B : NON A123123B : OUI $

Les regroupements peuvent eux-mmes tre associs par une alternative \| :


$ ./regexp.sh 'A\(12\)\|\(34\)B' A12B A34B A14B A12B : OUI A34B : OUI A14B : NON $

Rfrences arrire

Les regroupements peuvent servir lorsque la mme squence de caractres doit se retrouver plusieurs emplacements dans la mme chane. On peut alors insrer un indicateur qui fera rfrence une portion de lexpression rgulire en correspondance. Le symbole \1 reprsente la sous-chane qui est mise en correspondance avec le premier regroupement

Expressions rgulires Grep CHAPITRE 7

189

de lexpression rationnelle, \2 la portion de la chane associe au deuxime regroupement, et ainsi de suite. Ainsi, si notre expression rationnelle commence par \(.\)x\(..\), le symbole \1 fera rfrence au caractre de la chane mis en correspondance avec le premier groupement (qui, en loccurrence, ne contient quun caractre), et le symbole \2 aux deux caractres correspondant au second regroupement. On remarquera que, si la notation .\{3\} rclame trois caractres quelconques, \(.\)\1\1 rclame trois fois le mme caractre, tout comme \(.\)\1\{2\}:
$ ./regexp.sh 'A.\{3\}B' A123B A222B A123B : OUI A222B : OUI $ ./regexp.sh 'A\(.\)\1\1B' A123B A222B A123B : NON A222B : OUI $

Le mcanisme des rfrences arrire recle une grande puissance, mais la complexit de lcriture rend les expressions rgulires trs difciles lire et maintenir, limitant en pratique leur utilisation relle.

Expressions rationnelles tendues


Les expressions rationnelles tendues ne sont pas trs diffrentes des expressions simples. Le changement va concerner les caractres qui sont employs comme symboles spciaux et la cohrence avec lutilisation du backslash. La table suivante rsume les notations des symboles employs dans les expressions rationnelles, en indiquant les diffrences entre expressions simples et tendues.
Signication Symbole pour expression rguliere simple Symbole pour expression regulire tendue

Caractre gnrique Dbut de ligne Fin de ligne Alternative Liste de caractres Classe de caractres (dans une liste) Juxtaposition de caractres (dans une liste) Classe dquivalence (dans une liste) Zro, une ou plusieurs occurrences de llment prcdent Une ou plusieurs occurrences de llment prcdent Zro ou une occurrence de llment prcdent

. ^ $ \| [ ]

. ^ $ | [ ]

[:classe:] [.squence.] [=classe=] *


\+ \?

[:classe:] [.squence.] [=classe=] *


+ ?

190

Shells Linux et Unix par la pratique

Signication

Symbole pour expression rguliere simple


\{n,m\} \{n,\} \{0,m\} \{n\} \( \n \ \)

Symbole pour expression regulire tendue


{n,m} {n,} {0,m} {n} ( \n \ )

Au moins n et au plus m occurrences de llment prcdent Au moins n occurrences de llment prcdent Au plus m occurrences de llment prcdent Exactement n occurrences de llment prcdent Regroupement de caractres Rfrence arrire au n-ime regroupement Prxe dun caractre spcial pour reprendre sa valeur littrale

Naturellement, dans les expressions rationnelles tendues, les caractres |, +, ?, {, }, (, et ), qui deviennent spciaux, doivent tre prxs par un backslash pour retrouver leur valeur littrale, ce qui ntait pas ncessaire dans les expressions simples.

Outil grep
Le logiciel grep est un outil indispensable tant pour ladministrateur systme que pour le programmeur. Il permet de parcourir des chiers pour rechercher les lignes qui contiennent une expression rationnelle. Voici sa syntaxe habituelle :
grep [options] expression fichiers...

Les options qui nous concernent ici sont surtout : -E: les expressions rgulires sont tendues ; par dfaut, grep emploie des expressions rationnelles simples. Si on linvoque sous le nom egrep (dconseill), il adopte le mme comportement quavec cette option. -F: lexpression recherche nest pas une expression rgulire mais une simple chane de caractres, mme si elle contient des caractres qui seraient spciaux pour une expression rationnelle. Ceci est trs utile lorsque la chane recherche est fournie par une source que nous ne matrisons pas dans le script (lutilisateur par exemple). Linvocation de grep sous le nom fgrep (dconseill), a un effet identique cette option. -i: ignorer les diffrences entre majuscules et minuscules. -v: afcher les lignes qui ne contiennent pas lexpression rationnelle. Assez souvent, lexpression rationnelle est rduite une simple chane de caractres constante :
$ grep F "snmp" /etc/services snmp snmp-trap $ 161/udp 162/udp snmptrap # Simple Net Mgmt Proto # Traps for SNMP

Expressions rgulires Grep CHAPITRE 7

191

Quand on ne donne pas de nom de chier, grep recherche le motif dans les lignes qui proviennent de son entre standard :
# tcpdump -lnq -i eth0 | grep "192\.1\.1\.[[:digit:]]*\.telnet" Kernel filter, protocol ALL, datagram packet socket tcpdump: listening on eth0 13:18:53.154051 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:53.154135 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 81 (DF) 13:18:53.373837 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:53.373919 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 135 (DF) 13:18:53.593473 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:53.593560 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 136 (DF) 13:18:53.813304 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:53.813385 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 136 (DF) 13:18:54.032927 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:54.033010 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 136 (DF) 13:18:54.252612 < 192.1.1.60.1067 > 192.1.1.51.telnet: tcp 0 (DF) 13:18:54.252701 > 192.1.1.51.telnet > 192.1.1.60.1067: tcp 136 (DF) (Contrle-C) 12 packets received by filter #

Pour voir des expressions rgulires tendues seule la prsence du backslash les diffrencie des simples le script regexpext.sh est une copie de regexp.sh dans laquelle on a ajout loption E lors de lappel de grep. Invoquons-le en utilisant les exemples des prcdents paragraphes :
$ ./regexpext.sh 'A(12)|(34)B' A12B A34B A14B A12B : OUI A34B : OUI A14B : NON $ ./regexpext.sh 'A.{3}B' A123B A222B A123B : OUI A222B : OUI $ ./regexpext.sh 'A(.)\1\1B' A123B A222B A123B : NON A222B : OUI $

192

Shells Linux et Unix par la pratique

Recherche rcursive avec nd


Les utilisateurs dbutant sous Unix sont frquemment confronts au problme que pose la recherche rcursive dune chane de caractres dans tous les chiers qui se trouvent dans les sous-rpertoires partir dun point de dpart donn. Par exemple, cela est trs utile dans une arborescence de chiers source, pour rechercher toutes les invocations dune routine, toutes les utilisations dune variable, etc.
grep, seul, nest pas capable de raliser ce travail1 ; il nexamine que les chiers dont les noms lui sont fournis en arguments. La premire ide consiste invoquer loutil find

pour rechercher tous les noms de chiers partir dun point de dpart, et de les transmettre grep. Le problme qui se pose est que find envoie la liste des chiers trouvs sur sa sortie standard, alors que grep attend cette liste sur la ligne de commande. Cest loccasion rve demployer lutilitaire xargs que nous avons aperu dans le chapitre prcdent et qui est justement conu pour ce type de situation. Nous appelons find avec loption -type f pour quil ne sintresse quaux chiers normaux et il envoie la liste sur sa sortie standard. Celle-ci est transmise xargs qui va construire une ligne de commande en invoquant grep. Jemploie souvent ce petit utilitaire pour rechercher des symboles dans les sources de projets volumineux :
$ cd /usr/src/linux/drivers/ $ find . type f | xargs grep SIGKILL ./block/md.c: send_sig(SIGKILL, thread->tsk, 1); ./char/ftape/RELEASE-NOTES:SIGKILL (kill -9) will generate a sure kill. ./char/ftape/RELEASE-NOTES: LocalWords: SIGKILL MTIOCFTCMD mmap Iomega FDC fdcio gnumt mtio fc asm inb ./char/ftape/lowlevel/ftape-init.h:#define _NEVER_BLOCK (sigmask(SIGKILL) | sigmask(SIGSTOP)) ./char/sysrq.c: ./char/sysrq.c: ./char/tty_io.c: ./char/tty_io.c: ./char/vt.c: ./scsi/scsi.c: send_sig_all(SIGKILL, 0); send_sig_all(SIGKILL, 1); send_sig(SIGKILL, p, 1); send_sig(SIGKILL, p, 1); if (arg < 1 || arg > _NSIG || arg == SIGKILL) send_sig(SIGKILL, shpnt->ehandler, 1);

./scsi/scsi_error.c:#define SHUTDOWN_SIGS (sigmask(SIGKILL)|sigmask(SIGINT)|sigmask(SIGTERM)) $

1. La version GNU de grep dispose dune option R permettant la recherche rcursive, mais elle nest pas standard, et ne permet pas de ltrer les chiers parcourus (sur leur noms par exemple).

Expressions rgulires Grep CHAPITRE 7

193

Conclusion
Nous avons examin dans ce chapitre les expressions rationnelles, en ne laissant de ct que les lments les moins utiliss. Il est important de bien se familiariser avec lemploi de ces expressions, ce qui peut se faire en jouant avec grep, ou nos petits programmes regexp.sh, et regexpext.sh.

Exercices
Je propose au lecteur dobserver successivement les lignes du tableau suivant et dessayer de dterminer si lexpression rgulire prsente en premire colonne peut tre mise en correspondance avec la chane propose en seconde colonne. Essayez ensuite dutiliser le programme regexpext.sh pour vrier la rponse. Les solutions fournies dans le chapitre 11 sont commentes, car de nombreux cas ne sont pas vidents du tout Attention bien encadrer les expressions rgulires et les chanes par des apostrophes pour viter que le shell ne les modie.
Expression chane Correspondance ?

a* a* ^a*$ a$ a$ a. a+b $ $$ \$$ ^$ $^ \$\^ ^.{2}$ ^.{2}$ ^(.)\1$ ^(.)\1$

a b b a a$ a a+b a a $ ^$ $^ $^ aa ab aa ab

194

Shells Linux et Unix par la pratique

Expression

chane

Correspondance ?

[b-a] [^-_] [^-_] [^-_] []-[] [][-]

b _ ^ -

8
Sed
Sed est un outil souvent mal connu des personnes rcemment venues linformatique, et plus particulirement au systme Unix, et cest regrettable. Cet utilitaire peut rendre de grands services au sein de scripts shell qui automatisent des tches administratives, mais galement pour manipuler des chiers de texte, par exemple des pages HTML.

Prsentation
Sed est labrviation de Stream Editor, que lon peut comprendre comme diteur de texte orient ux . Pour saisir ce que cela signie, il faut dabord revenir lun des premiers outils du systme Unix : lditeur ed, dailleurs toujours disponible sur les systmes Unix actuels. Il sagit dun diteur de texte, comme vi, emacs, nedit, etc. ; toutefois, contrairement ces derniers, il ne fonctionne pas sur une page complte de texte afche lcran, mais sur une seule ligne la fois. Les utilisateurs qui ont frquent autrefois le monde DOS reconnatront peut-tre le mme concept que celui qui tait employ pour loutil edlin, avec lequel les modications des chiers systme pouvaient devenir de vritables preuves de trapze volant ! Lutilit des diteurs ligne ligne tait vidente avec certains terminaux qui ne permettaient pas dadresser le curseur de travail sur lensemble de lcran. Ces terminaux nacceptaient que la frappe dune seule ligne de texte, et la touche de validation (quivalente Entre) demandait lenvoi de cette saisie vers lunit centrale suivi du dlement de tout lcran dune ligne vers le haut. Il ntait plus possible de modier le texte qui se trouvait au-dessus de la ligne ddition, ni a fortiori de dplacer le curseur sur la page

196

Shells Linux et Unix par la pratique

pour travailler sur plusieurs lignes en mme temps. Ces terminaux ont pratiquement disparu de nos jours, mais jen ai encore rencontr, il y a quelques annes, relis un gros systme mainframe grant des bases de donnes militaires. Un diteur ligne ligne fonctionne donc en acceptant des commandes qui permettent dentrer une nouvelle ligne (par exemple, a pour lajouter aprs la ligne en cours, ou i pour linsrer avant celle-ci), dafcher certaines lignes (p), ou de substituer des expressions (s). Pour slectionner la ou les lignes sur lesquelles elles doivent agir, les commandes acceptent des numros, des intervalles, ou des expressions rationnelles. Le lecteur intress pourra se reporter la page de manuel de ed pour avoir plus de dtails sur les commandes. Lun des avantages de ed est que son interface utilisateur est limite aux ux dentre et sortie standards. On peut ainsi les rediriger pour crer des scripts de manipulation des chiers (de nos jours on parlerait plutt de macros). On devine lintrt que peut prsenter ce mcanisme pour rdiger des scripts qui automatisent les manipulations de chiers de texte. Le fait de devoir travailler obligatoirement sur des chiers limite nanmoins les possibilits dutilisation dans des chanes de commandes. Aussi, ds 1973, vit-on apparatre dans un premier temps un outil qui travaillait sur son ux dentre standard plutt que sur un chier et qui ralisait lafchage de toutes les lignes contenant une expression rationnelle. Dans la page de manuel de ed, cette commande est toujours prsente sous la forme g/re/p (global / regular expression / print), qui servit nommer loutil Grep tudi prcdemment. Dans un second temps, on ralisa limplmentation dune version de ed qui travaillait uniquement sur son ux dentre, tout en obtenant les ordres depuis des chiers scripts. Cette version oriente ux fut nomme Stream Editor, et abrge en sed.

Utilisation de Sed
On peut lgitimement sinterroger sur la pertinence quil y a utiliser Sed, ainsi que dautres outils comme Awk, pour manipuler de nos jours des chiers de texte ASCII brut. Aprs tout, il sagit dinstruments conus pour linformatique des annes 1970 ; leur emploi se justie-t-il encore lre du son et des images numriques ? Naturellement cela dpend de lusage que lon fait de ces chiers et de la ncessit ou non de rpter ultrieurement les mmes traitements sur diffrents chiers. Dans le monde professionnel, la rdaction dun document passe systmatiquement par un traitement de texte WYSIWIG (What You See Is What You Get). Certaines personnes le regrettent et prfrent employer des logiciels de traitement de documents comme TeX, LaTeX, LyX, troff, mais force est de constater que lusage courant a standardis certaines applications comme Word. Hors du monde tudiant, il est rare de rencontrer des documents importants rdigs sous dautres formats.

Sed CHAPITRE 8

197

Quoi quil en soit, les choses continuent voluer, et un nouveau besoin se fait de plus en plus pressant, surtout dans les environnements techniques : disposer simultanment de deux versions de la mme documentation. La version imprime, soigneusement mise en page et correctement relie, est prfrable pour une consultation prolonge, confortable, tandis que la version lectronique en ligne est indispensable pour une recherche rapide ou une consultation distance. De nouveaux formats mergent (HTML, SGML, XML, etc.) qui permettent une conversion plus ou moins aise vers le support employ pour la consultation. On peut raisonnablement imaginer quune part non ngligeable des documents techniques venir seront disponibles dans ces formats. En outre, Sed et Awk sont des outils absolument indispensables pour ladministrateur systme qui doit exploiter des chiers de trace (log les), ou manipuler automatiquement des chiers de conguration sur une machine ou un parc complet.

Principe
Le programme sed va agir sur les lignes dun chier de texte ou de son entre standard, et fournir les rsultats sur sa sortie standard. En consquence, les instructions de manipulation doivent tre fournies dans des chiers de scripts indpendants, ou en ligne de commande. La syntaxe dinvocation est la suivante :
$ sed -e liste_d_instructions fichier__traiter

Ou :
$ sed -f fichier_script fichier__traiter

Dans le premier cas, les oprations raliser sont indiques directement sur la ligne de commande. Dans le second, elles sont regroupes dans un chier script distinct. De nos jours, cette deuxime utilisation est devenue anecdotique et ne sera pas traite dans ce livre. Si aucun chier traiter nest indiqu, sed attend les donnes sur son entre standard. Lorsquon fournit directement les commandes sur la ligne, grce loption -e, il est prfrable de les inclure entre apostrophes simples, en raison de lusage frquent des caractres $, *, ?, etc., susceptibles dtre interprts par le shell. Une option importante est galement disponible : -n, avec laquelle sed fonctionne en mode silencieux, cest--dire quil ne copie une ligne de texte de lentre standard vers la sortie standard que si on le lui demande explicitement, et non pas automatiquement comme cest le cas par dfaut. Nous dtaillerons cela ultrieurement. Cette capacit traiter les informations au vol dans les ux dentre-sortie standards fait que lon utilise frquemment sed au sein de pipelines regroupant dautres commandes systme.

198

Shells Linux et Unix par la pratique

Fonctionnement de Sed
Ce traitement immdiat du texte lu dicte le fonctionnement mme de sed, puisquil ne dispose pas dune vue globale sur le chier traiter, mais uniquement des informations fractionnaires, dlivres sur le ux dentre standard. Ainsi Sed repose-t-il sur le mcanisme suivant : lecture dune ligne sur le ux dentre (jusqu ce quil rencontre un caractre de saut de ligne) ; traitement de cette ligne en la soumettant toutes les commandes rencontres dans le chier script ; afchage de la ligne rsultante sur la sortie standard, sauf si sed est invoqu avec loption n; passage la ligne suivante, et ainsi de suite jusqu la n du ux dentre standard. Les commandes que sed accepte ne sont pas trs nombreuses, et sont toujours reprsentes par une lettre unique (a, b, c, d, g, h, i, n, p, q, r, s, t, y) ou par le signe =. Il sagit essentiellement dinsertion ou de suppression de lignes, et de modication de chanes de caractres. En pratique, seules trois commandes sont utilises rellement (d, p, et s), les autres donnant des lignes illisibles et surtout impossibles maintenir. Nous allons examiner ces trois commandes ci-aprs. Une commande peut tre ventuellement prcde dune adresse. Dans ce cas, elle ne sapplique quaux lignes concernes du ux dentre standard. Une adresse se prsente sous forme numrique correspondant alors au numro de la ligne slectionne ou sous forme dexpression rgulire. Les numros de ligne commencent 1 et se poursuivent sans interruption sur lensemble des chiers traiter. Une expression rationnelle permet de slectionner une ou plusieurs lignes en fonction de leur contenu. Nous verrons que la slection peut galement se faire sur un intervalle de lignes, en indiquant deux adresses spares par une virgule. Des espaces ou des tabulations peuvent tre insrs volont en dbut de ligne, ainsi quentre ladresse et la commande. Pour tester sed, commenons par chercher un petit chier de texte sur notre systme :
$ ls -l /etc/host* -rw-r--r--rw-r--r--rw-r--r--rw-r--r-# 1 root 1 root 1 root 1 root root root root root 17 jui 23 2000 /etc/host.conf 466 jun 25 19:39 /etc/hosts 161 jan 13 2000 /etc/hosts.allow 347 jan 13 2000 /etc/hosts.deny

$ cat /etc/hosts.allow

Sed CHAPITRE 8

199

# hosts.allow This file describes the names of the hosts which are # # # $ allowed to use the local INET services, as decided by the /usr/sbin/tcpd server.

Parfait ! Tout dabord, nous allons demander sed dappliquer la commande p (print) aux lignes du chier, sans aucun ltrage. Cette commande demande lafchage explicite de la ligne en cours. Comme loption -n nest pas employe, sed effectue aussi une copie systmatique des donnes lues vers la sortie standard, ce qui a pour effet de dupliquer les lignes :
$ sed -e p # # # hosts.allow # hosts.allow # # # # # # < /etc/hosts.allow

This file describes the names of the hosts This file describes the names of the hosts allowed to use the local INET services, as allowed to use the local INET services, as by the /usr/sbin/tcpd server. by the /usr/sbin/tcpd server.

which are which are decided decided

En revanche, lorsquon utilise -n, les lignes ne sont afches quavec la commande p explicite :
$ sed -n -e p < /etc/hosts.allow # # hosts.allow This file describes the names of the hosts which are # allowed to use the local INET services, as decided # by the /usr/sbin/tcpd server. # $

200

Shells Linux et Unix par la pratique

Nous pouvons examiner le ltrage des lignes, en demandant une slection du numro de ligne :
$ sed -n -e 2p < /etc/hosts.allow # hosts.allow This file describes the names of the hosts which are $ sed -n -e 4p < /etc/hosts.allow # $ by the /usr/sbin/tcpd server.

On peut aussi slectionner une ligne en indiquant un motif (sous forme dexpression rgulire) quelle doit contenir. Ce motif est indiqu entre caractres slashes / (barres obliques) :
$ sed -n -e /file/p < /etc/hosts.allow # hosts.allow This file describes the names of the hosts which are $ sed -n -e /serv/p < /etc/hosts.allow # allowed to use the local INET services, as decided # by the /usr/sbin/tcpd server. $

La commande p, accepte une slection de lignes sous forme dintervalle. Un intervalle est dcrit par deux adresses spares par une virgule. Le cas le plus vident est celui de deux adresses numriques, mais il est galement possible dutiliser des expressions rgulires, quoique la lecture de la commande devienne plus difcile.
$ sed -n -e 2,4p < /etc/hosts.allow # hosts.allow This file describes the names of the hosts which are # # allowed to use the local INET services, as decided by the /usr/sbin/tcpd server.

$ sed -n -e /hosts/,/services/p < /etc/hosts.allow # hosts.allow This file describes the names of the hosts which are # $ allowed to use the local INET services, as decided

La slection par un intervalle se fait en fonction des rgles suivantes : sed slectionne la premire ligne correspondant la premire expression rationnelle ou au numro indiqu avant la virgule ; il slectionne galement toutes les lignes suivantes jusqu ce quil en rencontre une qui corresponde la seconde expression rgulire ou au numro indiqu aprs la virgule ;

Sed CHAPITRE 8

201

les lignes la suite ne sont pas slectionnes, jusqu ce quil en rencontre une qui corresponde nouveau au premier critre. En consquence, plusieurs remarques simposent : Si la seconde partie de lintervalle est une expression rationnelle, sa mise en correspondance nest tente qu partir de la ligne qui suit celle qui est slectionne par la premire partie. Un intervalle form en seconde partie dune expression rgulire slectionne toujours au moins deux lignes. On notera toutefois que ce comportement, standardis de nos jours, peut varier sur des versions anciennes de sed. En revanche, une slection du type 2,2, ou /hosts/,2 peut trs bien ne slectionner quune ligne. Cela est galement vrai pour une slection du type 4,1 qui ne slectionne que la ligne 4, ou /hosts/2 si le mot host napparat quen ligne 3 par exemple. Lorsque aucune ligne ne correspond la seconde partie de lintervalle (expression rgulire impossible trouver ou numro suprieur ou gal au nombre de lignes), la slection sapplique jusqu la n du chier. Comme sed lit les lignes dentre une une, il na aucun moyen de savoir lavance si une correspondance sera possible. Il faut donc voir les intervalles comme des bascules slection / dslection, et pas comme de vritables recherches de portions de texte. Enn, un intervalle dcrit par des expressions rgulires peut se rpter dans le texte. Ceci est trs utile lorsquon veut extraire dun chier des portions dnies par des indicateurs (un marqueur de dbut et un autre de n) qui se rptent plusieurs emplacements. Ladresse symbolique $ correspond la dernire ligne du dernier chier traiter. On notera galement que lon peut nier une adresse, cest--dire nexcuter la commande que sur les lignes qui ne correspondent pas la slection grce loprateur !. Par exemple, nous nafchons ici que les lignes qui ne sont pas vides :
$ sed -n -e /^$/!p < /etc/hosts.allow # # hosts.allow This file describes the names of the hosts which are # # # $ allowed to use the local INET services, as decided by the /usr/sbin/tcpd server.

Commandes Sed
On peut utiliser la commande p vue ci-dessus surtout pour deux raisons principales : Afcher la n-ime ligne (sed ne "$Np"), ou les lignes de la n-ime la m-ime dun chier (sed ne "$N,$Mp"). Ceci est galement possible en enchanant les commandes tail et head mais de manire moins lgante.

202

Shells Linux et Unix par la pratique

Afcher les lignes dans un intervalle dlimit par des expressions rgulires. Cest en quelque sorte une extension de la commande grep qui nafche que les lignes correspondant une expression, mais pas un intervalle.
Suppression de ligne

La commande d (delete) permet de supprimer la ligne slectionne. Naturellement, comme sed travaille sur un ux de donnes et pas directement sur un chier, il ne sagit pas dune vritable suppression, mais plutt dun abandon. En rencontrant cette commande, sed passe simplement la ligne suivante sans afcher celle en cours. On utilise la commande d de manire symtrique la commande p, lorsquon sait slectionner ce que lon veut rejeter, pour garder tout le reste :
$ sed ne /selection_a_conserver/p

est symtrique :
$ sed e /selection_a_rejeter/d

(Notez labsence doption -n dans le second cas, pour conserver lafchage automatique des lignes.) Voyons, par exemple, la suppression de la ligne contenant le mot hosts:
$ sed -e /hosts/d < /etc/hosts.allow # # # # allowed to use the local INET services, as decided by the /usr/sbin/tcpd server.

Nous pouvons galement supprimer toutes les lignes sauf celles qui contiennent un motif ( the par exemple) :
$ sed -e /the/!d < /etc/hosts.allow # hosts.allow This file describes the names of the hosts which are # # $ allowed to use the local INET services, as decided by the /usr/sbin/tcpd server.

Sed CHAPITRE 8

203

Bien que lon puisse placer plusieurs commandes successives en argument de loption -e, en les sparant laide dun point-virgule, je conseille de les clater en plusieurs invocations de sed enchanes par des pipes | au niveau du shell. Lexcution sera un peu moins efcace car on aura un plus grand nombre de processus mais on gagnera en lisibilit si les tches successives sont clairement distingues. Par exemple, si lon dsire supprimer toutes les lignes blanches, ou celles qui dbutent par un caractre dise (#) on peut crire :
$ sed e /^[[:blank:]]*$/d; /^[[:blank:]]*#/d

Mais je prfrerais :
$ sed e /^ [[:blank:]]*$/d | sed e /^[[:blank:]]*#/d

Ou mieux :
supprime_commentaires.sh 1 2 3 4 5 6 7 8 9 10 11 #! /bin/sh for fic in "$@" do # supprimons les lignes blanches sed -e '/^[[:blank:]]*$/d' $fic | # supprimons les commentaires sed -e '/^[[:blank:]]*#/d' done

Commentaires

Il faut reconnatre que quelle que soit la bonne volont afche par le programmeur, la lisibilit dune commande sed est gnralement trs mauvaise. Cela est d la complexit des expressions rgulires employes dans les adresses, ds quun script dpasse quelques tches triviales. Une bonne habitude consiste donc faire prcder chaque commande sauf si elle est vraiment vidente dune ligne de commentaire prcisant dans quelles conditions elle sapplique. Ce commentaire doit tre crit avec soin, car il doit dcrire lintention du programmeur. Ce nest pas une paraphrase en franais de lexpression rationnelle, mais une explication des circonstances qui motivent le dclenchement de la commande. Par exemple, le commentaire suivant nest pas trs utile :
# Ajouter une ligne blanche aprs une ligne se terminant par un point.

204

Shells Linux et Unix par la pratique

Tandis que celui-ci indique bien lintention du programmeur :


# Ajouter une ligne blanche aprs chaque paragraphe.

Le manque lgendaire de lisibilit des commandes sed conduit qualier cet outil de langage en criture seule Jai rencontr un utilisateur expriment de Sed qui devait maintenir et faire voluer rgulirement des scripts chez des clients. Il ma avou quil tait parfois oblig dimprimer les diffrentes versions dun script, et de les comparer par transparence en superposant les listings devant une fentre pour retrouver les dernires modications !
Substitution

La commande essentielle de Sed, celle qui occupe une proportion norme des scripts courants, est la commande s (substitution). Elle permet de remplacer partiellement le contenu dune ligne. Il sagit de la tche principale des commandes sed, car les autres fonctions les plus utilises (d et p) peuvent souvent tre assures par dautres utilitaires (grep, head, tail, et tr par exemple) de manire moins directe, mais souvent moins effrayante pour lutilisateur courant. Bien que lon puisse faire beaucoup de choses avec Sed, son utilisation est souvent motive par la recherche et le remplacement de motifs au sein des lignes dun texte. En voici la syntaxe gnrale :
s/motif/remplacement/options

Naturellement, on peut faire prcder cette commande dune slection dadresse. Le motif indiqu est en premier lieu recherch dans la ligne en cours et, le cas chant, remplac par la chane qui se trouve en seconde position. Le motif est constitu dune expression rgulire simple, avec les caractres spciaux que nous avons observs dans le chapitre prcdent. Lorsque sed rencontre dans la ligne une expression qui correspond au motif recherch, il la remplace avec la chane de caractres fournie en second argument. Dans cette expression, seuls deux mtacaractres peuvent tre utiliss :
Caractre Signication
Sera remplac par la chane de caractres complte qui a t mise en correspondance avec le motif. i tant un nombre entre 1 et 9, il est remplac par la i-ime sous-expression rgulire encadre par des parenthses dans le motif initial.

& \i

Le caractre \ permet naturellement de protger le & ainsi que \ lui-mme, an quils ne prennent pas leur signication particulire. Il faut imprativement employer lexpression \& pour obtenir le caractre & dans la chane de remplacement. Cet oubli frquent est une cause importante derreurs dans les scripts qui manipulent des chiers HTML ou C, o le caractre & est couramment employ.

Sed CHAPITRE 8

205

Les options possibles la n de la commande de substitution sont les suivantes :


Option Signication
Remplacer tous les motifs rencontrs dans la ligne en cours. Cette option est presque toujours employe. Ne remplacer que la i-ime occurrence du motif dans la ligne. Cela peut surtout servir lorsquon manipule des chiers qui reprsentent des lignes denregistrements, contenant des champs spars par des dlimiteurs. Afcher la ligne si une substitution est ralise. Suivie dun nom de chier, cette option permet dy envoyer le rsultat de la substitution. Cela sert gnralement des ns de dbogage.

g i p w

Les commandes de substitution sont trs importantes et lon doit voir de prs leur fonctionnement. Nous allons commencer par examiner quelques cas simples.
Remplacement dune seule occurrence dune chane complte

Nous envoyons, grce la commande echo du shell, la chane azerty azerty azerty vers lentre standard de sed, et lui demandons de remplacer az par qw. Loption globale (g) nest pas demande.
$ echo "azerty azerty azerty" | sed -e "s/az/qw/" qwerty azerty azerty

Nous demandons prsent que soit remplace la seconde occurrence du motif :


$ echo "azerty azerty azerty" | sed -e "s/az/qw/2" azerty qwerty azerty

Remplacement de toutes les occurrences dune chane

Avec loption g, toutes les occurrences sont remplaces :


$ echo "azerty azerty azerty" | sed -e "s/az/qw/g" qwerty qwerty qwerty

Extension de la chane

La mise en correspondance se fait de telle sorte que le motif consomme le maximum de caractres. Nous allons demander sed de remplacer un motif constitu dun a, dune chane de longueur quelconque, et dun z. Nous pouvons remarquer quil va assurer la substitution de la plus longue sous-chane possible en allant chercher le z le plus loign.
$ echo "azerty azerty azerty" | sed -e "s/a.*z/qw/g" qwerty

206

Shells Linux et Unix par la pratique

Remplacement de caractres spciaux

Les chiers de texte utiliss sous Unix terminent les lignes par un caractre saut de ligne reprsent gnralement par le symbole \n de code ASCII 0A. Sous DOS, par exemple, les lignes de texte doivent se terminer par une squence retour chariot/saut de ligne. Il faut donc faire prcder le 0A dun caractre de code ASCII 0D, symbolis par \r. Si lon change frquemment des chiers de texte entre ces systmes, il est ncessaire dutiliser des petits outils de conversion capables dajouter ou de supprimer le caractre \r adquat. Ces outils peuvent trs bien tre raliss avec sed. Lcriture dun script dos_2_unix avec sed correspond la suppression (substitution par une chane vide) du caractre \r en n de ligne. Pour visualiser ce caractre, nous utilisons loption -t de lutilitaire cat, qui demande un afchage des caractres spciaux sous forme symbolique (^M en loccurrence) :
$ cat -t autoexec.bat ^M mode con codepage prepare=((850) c:\windows\COMMAND\ega.cpi)^M mode con codepage select=850^M keyb fr,,c:\windows\COMMAND\keyboard.sys^M ^M SET PATH=C:\Perl\bin;%PATH%^M $

La manipulation avec sed est la suivante :


$ sed e s/^M$// autoexec.bat | cat -t

mode con codepage prepare=((850) c:\windows\COMMAND\ega.cpi) mode con codepage select=850 keyb fr,,c:\windows\COMMAND\keyboard.sys

SET PATH=C:\Perl\bin;%PATH% $

Pour saisir le retour chariot (reprsent par ^M), il faut utiliser en gnral la squence de touches Contrle-V Contrle-M.
$ sed e 's/$/^M/'

Sed CHAPITRE 8

207

Utilisation dun ensemble

Les ensembles de caractres sont trs souvent utiliss pour mettre en correspondance des chanes, sans tenir compte des majuscules et des minuscules.
$ echo "Azerty azerty aZerty" | sed -e "s/[Aa][Zz]/qw/g" qwerty qwerty qwerty

On peut dailleurs en proter pour insrer les caractres accentus franais et crire un petit script qui les limine pour revenir au jeu de caractres ASCII standard. Le petit script suivant traite une partie des caractres spciaux du jeu ISO-8859-1 (Latin-1), et les remplace par leurs quivalents appauvris du code ASCII.
latin1_en_ascii.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #! /bin/sh sed -e 's/[]/A/g' sed -e 's//AE/g' sed -e 's//C/g' sed -e 's/[]/E/g' sed -e 's/[]/I/g' sed -e 's//N/g' sed -e 's/[]/O/g' sed -e 's/[]/U/g' sed -e 's/Y/Y/g' sed -e 's/[]/a/g' sed -e 's//ae/g' sed -e 's//c/g' sed -e 's/[]/e/g' sed -e 's/[]/i/g' sed -e 's//n/g' sed -e 's/[]/o/g' sed -e 's/[]/u/g' y sed -e 's//y/g' | | | | | | | | | | | | | | | | |

Ce script pourrait trs bien tre tendu pour couvrir au mieux lensemble des caractres Latin-1 disponibles. On remarquera que la conversion en AE, par exemple, naurait pas t possible avec lutilitaire tr, qui traduit caractre par caractre.
$ echo "Caractres accentus en franais" | ./latin1_en_ascii.sh Caracteres accentues en francais $

208

Shells Linux et Unix par la pratique

Remplacement du caractre &

Le mtacaractre & remplace toute la chane de caractres mise en correspondance avec lexpression rationnelle en premier argument. Par exemple, nous pouvons lemployer pour encadrer un mot (Linux en loccurrence) par les balises HTML <B> et </B> rclamant un afchage en caractres gras. Il faut protger le caractre / de la balise </B> par un \:
$ echo "Le systme Linux 2.6" | sed -e "s/[Ll]inux/<B>&<\/B>/g" Le systme <B>Linux</B> 2.6 $

On notera que le caractre utilis pour sparer les arguments de la commande de substitution nest par obligatoirement /. On emploie ce dernier par convention et il est conseill de sy plier pour garder une bonne lisibilit mais nimporte quel caractre pourrait fonctionner :
$ echo "Le systme Linux 2.6" | sed -e s![Ll]inux!<B>&</B>!g Le systme <B>Linux</B> 2.6 $

Dans ce dernier exemple, il nest plus ncessaire de protger le /. En revanche, il faut encadrer la chane par des apostrophes simples, car le caractre ! est employ par Bash pour grer lhistorique des commandes et il tenterait dinterprter la chane si elle tait encadre par des guillemets. On emploie gnralement & pour effectuer le genre dopration que lon vient de dcrire (encadrement, prxage, etc.) et pour modier lalignement des lignes de texte.
Rfrences arrire

Lorsque des parties de lexpression rgulire ont t encadres par des parenthses (elles-mmes protges par des \) dans le motif recherch, il est possible dy faire rfrence dans lexpression de remplacement, en utilisant les symboles \1 pour la premire sous-expression, \2 pour la deuxime, et ainsi de suite. Par exemple, nous allons crire une commande de substitution qui va servir rordonner les divers champs dune date. Pour simplier lexpression rationnelle, qui est dj bien assez complique comme cela, nous utiliserons le sparateur - entre les constituants de la date, au lieu du / habituel, quil aurait fallu protger de surcrot. Nous crivons lexpression rationnelle en distinguant trois sous-expressions, chacune encadre par \( et \). Les sous-expressions contiennent toutes un intervalle recouvrant les chiffres 0 9, suivi dun astrisque autorisant la rptition.

Sed CHAPITRE 8

209

Le motif de remplacement est constitu de trois rfrences arrire vers les sous-expressions obtenues, ces rfrences tant spares par des tirets. Nous inversons alors lordre pour retrouver un schma jour-mois-anne.
$ date +"%y-%m-%d" 01-03-30 $ date +"%y-%m-%d" | sed -e s/\([0-9]*\)-\([0-9]*\)-\([0-9]*\)/\3-\2-\1/ 30-03-01 $

Lemploi de rfrences arrire est naturellement trs puissant, puisquil permet de modeler volont les composantes dune expression rgulire. On est ainsi amen scinder lexpression recherche en plusieurs morceaux, pour en extraire certaines parties centrales, ce qui pose des problmes de lisibilit. Il est conseill dessayer de lire lexpression en essayant tout dabord de faire abstraction des parenthses de regroupement \( et \) an de bien comprendre comment la mise en correspondance est effectue. Les diffrentes parties de lexpression peuvent ensuite tre distingues pour analyser la chane de remplacement.
Insertion de sauts de ligne

Lorsquil faut insrer un saut de ligne dans le cours dune ligne slectionne, la mthode la plus portable est dutiliser une substitution dans laquelle la seconde expression stend sur deux lignes, la premire tant termine par un backslash. On peut employer les rfrences arrire pour replacer les motifs de lexpression rgulire servant trouver le point dinsertion :
$ echo "Je fais souvent ce rve trange et pntrant" | sed e ' s/\(rve\) \(trange\)/\1\ \2/' Je fais souvent ce rve trange et pntrant

Toutefois le manque de lisibilit de ces commandes leur fera prfrer toute autre solution plus facile maintenir.

210

Shells Linux et Unix par la pratique

Autres commandes Sed

Il existe une vingtaine dautres commandes pour Sed, mais elles ne sont pratiquement jamais employes cause de leur mauvaise lisibilit. Nous en avons toutefois regroup quelques-unes dans le tableau ci-dessous :
Commande Syntaxe Usage
Ajout de texte aprs la ligne courante

a c i l n y =

a\ texte ajout c\ texte remplacement i\ texte insr L N y/sources/cibles/ =

Remplacer les lignes slectionnes par le texte fourni

Insrer du texte avant la ligne courante

Afcher les lignes slectionnes avec les caractres de contrle Sauter la n du script Convertir des caractres Afcher la ligne en cours (pas dintervalle)

Conclusion
Nous avons essay, dans ce chapitre, de prsenter les commandes les plus utiles pour les commandes Sed courantes. Dans le mme esprit, nous allons voir dans le chapitre suivant les utilisations les plus courantes de Awk.

Exercice
tant donn le peu de variations sur lutilisation de sed, je ne vous proposerai quun seul exercice : un script capable de convertir un chier contenant des caractres ISO Latin-1 (le jeu de caractres habituels des francophones), en squence HTML. Dans le langage HTML, un caractre accentu est remplac par une squence de plusieurs caractres ASCII commenant par un & et nissant par un point-virgule. Il existe plusieurs dizaines de caractres spciaux, mais je nai indiqu dans le tableau ci-dessous que les caractres utiliss en franais. Le lecteur dsireux de connatre les autres squences les obtiendra facilement en saisissant HTML Entities dans un moteur de recherche.
Latin-1

Html

&Agrave; &Acirc; &AElig;

Sed CHAPITRE 8

211

Latin-1

Html

&Ccedil; &Egrave; &Eacute; &Ecirc; &Euml; &Icirc; &Iuml; &Ocirc; &Ugrave; &Ucirc; &Uuml; &agrave; &acirc; &aelig; &ccedil; &egrave; &eacute; &ecirc; &euml; &icirc; &iuml; &ocirc; &ugrave; &ucirc; &uuml; &yuml;

Une remarque : attention au caractre & qui doit tre protg par un backslash pour perdre sa signication spciale dans la chane de remplacement.

9
Awk
Nous avons vu que Sed permet de raliser de nombreuses tches de manipulation de chiers. Awk rend possible des actions plus complexes encore, y compris des oprations mathmatiques, ou des squences logiques compltes. La dnomination de ce langage est due au regroupement des initiales de ses trois crateurs, Alfred Aho, Peter Weinberger et Brian Kernighan. Les fonctionnalits standards de Awk sont dcrites dans la norme Single Unix version 3, ce qui leur confre une bonne portabilit entre les diffrentes versions dUnix. Si Sed est rgulirement utilis pour rechercher et remplacer des sous-chanes dans des chiers de texte, Awk, pour sa part, est frquemment employ pour extraire les divers champs contenus dans chaque ligne denregistrement dun chier. Ce sont en quelque sorte leurs emplois naturels, ceux pour lesquels ladministrateur systme invoquera sans hsiter ces langages au sein dune chane de commandes shell.

Fonctionnement de Awk
Le principe gnral de Awk est le mme que celui de Sed : lecture des lignes de texte sur lentre standard, traitement en fonction dinstructions regroupes sur la ligne de commande ou dans un chier externe et criture des rsultats sur la sortie standard. Les commandes pour awk ont la forme :
motif { action }

o laction est applique chaque ligne dentre qui permet une mise en correspondance avec le motif.

214

Shells Linux et Unix par la pratique

Les motifs
Les motifs avec lesquels les lignes sont compares ressemblent quelque peu aux slections dj rencontres avec Sed, mais offrent plus de possibilits. Tout dabord, un motif peut tre compos dune expression rgulire exprime entre caractres slash. Par exemple, la commande /root/ {print} applique laction print (qui, on le devine, afche la ligne en cours sur la sortie standard) aux lignes contenant le mot root. Lorsque lentre standard provient du chier /etc/passwd, nous obtenons :
$ awk '/root/ {print}' < /etc/passwd root:x:0:0:root:/root:/bin/bash operator:x:11:0:operator:/root: $

Un motif de slection peut galement tre reprsent par une relation, cest--dire un test ralis sur un champ donn de la ligne. Nous dtaillerons ce principe plus bas mais, titre dexemple, la slection $1~/tcp/ vrie si le premier champ de la ligne (les champs sont par dfaut spars par des espaces) contient lexpression rationnelle tcp. En voici une illustration :
$ awk '$1~/tcp/{print}' < /etc/services tcpmux afpovertcp afpovertcp $ 1/tcp 548/tcp 548/udp # TCP port service multiplexer # AFP over TCP # AFP over TCP

Seule, la slection /tcp/ accepte aussi le motif tcp en seconde colonne, afchant bien plus de lignes :
$ awk '/tcp/{print}' < /etc/services tcpmux echo discard systat daytime [...] tfido fido $ 60177/tcp 60179/tcp # Ifmail # Ifmail 1/tcp 7/tcp 9/tcp 11/tcp 13/tcp # TCP port service multiplexer

Un motif de slection peut tre une combinaison logique de plusieurs expressions, laide des oprateurs boolens && (ET), || (OU), ! (NON), et mme un oprateur ? qui

Awk CHAPITRE 9

215

semploie sous la forme motif_1?motif_2:motif_3. Si le premier motif est valid, la mise en correspondance se fait avec le deuxime, sinon avec le troisime. On peut galement crire cela avec la forme (motif_1&&motif_2)||motif_3. Comme on le voit, on peut regrouper les motifs de slection entre parenthses pour grer les priorits. Comme avec sed, on peut slectionner des intervalles avec une forme motif_1,motif_2, cest--dire un ensemble de lignes qui commence avec la premire ligne correspondant au motif_1 jusqu ce que soit rencontre une ligne (comprise) correspondant au deuxime motif. Deux slections particulires, BEGIN et END, sont aussi disponibles. Une instruction qui commence par BEGIN est excute au dmarrage du programme, avant toute tentative de lecture. Symtriquement, une instruction END est excute aprs la n de tout le chier, juste avant la terminaison de awk. Par exemple, la ligne suivante permet dafcher simplement un message, quelles que soient les donnes fournies en entre :
$ awk 'BEGIN {printf "Hello World ! \n"}' Hello World ! $ awk 'BEGIN {printf "Hello World ! \n"}' < /dev/null Hello World ! $

La ligne BEGIN est trs utile pour linitialisation de awk, notamment quand il sagit de remplir les variables requises pour lexcution du programme.

Les actions
Une instruction Awk, nous lavons dit, est compose en premier lieu dun motif de slection et dune seconde partie qui contient les actions entreprendre sur les lignes de lentre standard ainsi slectionnes. Les actions sont regroupes entre accolades, et spares par des points-virgules et/ou des sauts de ligne :
$ awk 'BEGIN { print 1 ; print 2 }' 1 2 $ awk 'BEGIN { print 1 > print 2 } 1 2 $

216

Shells Linux et Unix par la pratique

Un jeu complet dactions est disponible avec Awk (entres-sorties, oprations arithmtiques, interactions avec le systme, manipulations de chanes de caractres), nanmoins force est de constater quune grande majorit des scripts Awk couramment employs se limite laction print. Pour tre honnte, il convient de prciser ds prsent que toutes les tches qui peuvent tre ralises avec Awk peuvent aussi tre effectues avec Perl. Lorsque le travail est vraiment plus complexe quune simple manipulation de lignes de texte, il est plus intressant de se tourner vers ce dernier, mme si sa mise en uvre est lgrement plus lourde. Tout programmeur srieux doit toutefois avoir une connaissance approfondie du langage Awk ne serait-ce qu titre de culture gnrale car de nombreux scripts systme emploient quelques lignes de commande Awk au sein de commandes composes. La comprhension du fonctionnement interne de ces scripts, leur maintenance et leur ventuelle adaptation passent par une certaine familiarit avec Awk. titre dexemple voici le nombre de chiers qui invoquent awk (mme sommairement) dans diffrents rpertoires de ma machine Linux Fedora :
Rpertoire Nombre de chiers appelant awk
7 9 46 6 130

/bin /sbin /usr/bin /usr/sbin /etc/...

On peut remarquer le grand nombre de scripts dadministration du systme (dans /etc et ses sous-rpertoires) qui invoquent awk. Linterprteur awk propose une option -f suivie du nom dun chier qui contient les instructions excuter. On peut ainsi crer des scripts Awk qui dbutent par une ligne shebang :
script.awk : 1 2 3 4 5 6 7 8 #! /usr/bin/awk -f BEGIN { print "**** dbut ****" } END { print "**** fin ****" } # Les instructions sans motif de slection sappliquent # toutes les lignes de lentre standard. { print "* " $0 }

Toutefois, cette utilisation est trs rare (aucun script Awk autonome dans lensemble des rpertoires mentionns ci-dessus), et nous ne la dtaillerons pas dans ce livre.

Awk CHAPITRE 9

217

Les variables
Awk autorise la cration et la consultation de variables, susceptibles de contenir aussi bien des chanes de caractres que des valeurs numriques en virgule ottante. Comme dans la plupart des langages interprts, les variables sont cres dynamiquement sans ncessiter de dclaration pralable. Une variable commence exister ds quon lui affecte une valeur. Lorsquune valeur doit tre initialise avec une valeur non nulle, on emploiera une instruction BEGIN. Voici un appel de awk pour ajouter un numro avant chaque ligne :
$ awk 'BEGIN { nb = 1 } { print nb " " $0; nb++ }' < /etc/passwd 1 root:x:0:0:root:/root:/bin/bash 2 bin:x:1:1:bin:/bin:/sbin/nologin 3 daemon:x:2:2:daemon:/sbin:/sbin/nologin 4 adm:x:3:4:adm:/var/adm:/sbin/nologin 5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin 6 sync:x:5:0:sync:/sbin:/bin/sync [] $

On remarquera quelques points : Lutilisation des variables est simple et intuitive, awk assurant la conversion des donnes numriques en chanes de caractres (et inversement) suivant le contexte dutilisation. On remarquera que, contrairement au shell, il ny a pas lieu dajouter de caractre particulier comme le $ pour accder au contenu dune variable. Dans la commande print, nous avons afch une variable particulire : $0. Elle reprsente toujours la ligne qui vient dtre lue sur lentre standard. La notation nb++ est un raccourci pour dire incrmenter la variable nb.

Enregistrements et champs
Les enregistrements
On considre que le chier dentre est constitu dune suite denregistrements, euxmmes composs de plusieurs champs. Par dfaut, les enregistrements correspondent aux lignes du chier dentre, spars donc par un caractre de saut de ligne, mais on peut modier ce sparateur pour lire des chiers organiss diffremment. La variable spciale RS (Record Separator) dcrit la sparation entre les enregistrements : lorsque RS est vide, la sparation se fait sur des lignes blanches, ce qui peut servir traiter un chier de texte paragraphe par paragraphe ;

218

Shells Linux et Unix par la pratique

lorsque RS contient un seul caractre, celui-ci sert de sparateur denregistrements ; par dfaut cest le saut de ligne ; lorsque RS contient plusieurs caractres, sa valeur est considre comme une expression rationnelle dcrivant les sparations. Si on veut indiquer plusieurs sparateurs possibles, il faut donc les regrouper entre crochets, en mentionnant ainsi une alternative. Voyons quelques exemples dapplications ; supposons tout dabord que nous consultions une base de donnes dont les enregistrements sont spars par des caractres #. Chaque enregistrement est compos de champs spars par des espaces, mais nous ne nous en soucions pas pour le moment. Notre chier contient donc :
$ cat base_1.txt champ1.1 champ1.2 champ1.3#champ2.1 champ2.2 champ2.3#champ3.1 champ3.2 champ3.3#champ4.1 champ4.2 champ4.3 $

Nous allons lire et afcher les enregistrements un un. Pour ce faire, nous initialisons la variable RS ds le dmarrage du script, dans une ligne BEGIN. Le caractre # sera le seul sparateur denregistrement. Nous employons ensuite une instruction print pour afcher dabord quelques caractres ce qui nous permet de bien voir les limites des enregistrements telles que awk les peroit , puis le contenu de lenregistrement en cours, qui est stock dans la variable spciale $0.
$ >> >> >> >> $ awk 'BEGIN{RS="#"} {print ">> " $0}' base_1.txt champ1.1 champ1.2 champ1.3 champ2.1 champ2.2 champ2.3 champ3.1 champ3.2 champ3.3 champ4.1 champ4.2 champ4.3

Dans notre second exemple, nous supposerons que la base de donnes contient des enregistrements encadrs par des accolades. Les champs peuvent se trouver sur des lignes diffrentes. Voici notre base :
$ cat base_2.txt { champ1.1 champ1.2 champ1.3 champ1.4 } { champ2.1 champ2.2 champ2.3 champ2.4 }

Awk CHAPITRE 9

219

{ champ3.1 champ3.2 champ3.3 champ3.4 } {champ4.1 champ4.2 champ4.3 champ4.4} {champ5.1 champ5.2 champ5.3 champ5.4} $

Le format est trs libre. Nous devons caractriser les sparations denregistrements par une expression rgulire. Exprimons-la dabord en franais : La sparation commence par un } facultatif. En effet, pour le premier enregistrement, ce caractre est absent. Nous aurons donc une expression }*. Ensuite peuvent venir autant despaces, de tabulations ou de sauts de ligne quon le souhaite. On crira donc [\n\t ]*. On rencontre ensuite un caractre {, sauf pour le dernier enregistrement. Enn, on peut retrouver un ou plusieurs caractres blancs. Notre expression rationnelle devra donc reprsenter soit un } ventuel, des blancs et un { suivi de blancs, soit un } seul. On utilise une alternative OU | en regroupant la premire expression entre parenthses : (}*[\n\t ]*{[\n\t ]*) | }. Essayons cette expression :
$ awk 'BEGIN{RS="(}*[\n\t ]*{[\n\t ]*)|}"} {print ">> "$0}' base_2.txt >> >> champ1.1 champ1.2 champ1.3 champ1.4 >> champ2.1 champ2.2 champ2.3 champ2.4 >> champ3.1 champ3.2 champ3.3 champ3.4 >> champ4.1 champ4.2 champ4.3 champ4.4 >> champ5.1 champ5.2 champ5.3 champ5.4 >> $

Notre dcoupage se fait correctement, mais nous pouvons remarquer quil engendre deux enregistrements vides, lun avant le premier enregistrement rel, et lautre aprs le

220

Shells Linux et Unix par la pratique

dernier. Ce problme frquent peut se rsoudre en utilisant une instruction supplmentaire en dbut de script, qui emploie la commande next, laquelle demande de passer lenregistrement suivant en abandonnant celui qui est en cours lorsquil ne contient aucun champ. Le nombre de champs est automatiquement inscrit par awk dans une variable spciale nomme NF (Number of Fields). Nous ajoutons donc une instruction avec un test de slection qui vrie si la variable NF est nulle.
$ <awk 'BEGIN{RS="(}*[\n\t ]*{[\n\t ]*)|}"} NF==0{next} {print ">> "$0}' base_2.txt >> champ1.1 champ1.2 champ1.3 champ1.4 >> champ2.1 champ2.2 champ2.3 champ2.4 >> champ3.1 champ3.2 champ3.3 champ3.4 >> champ4.1 champ4.2 champ4.3 champ4.4 >> champ5.1 champ5.2 champ5.3 champ5.4 $

Cette fois-ci, les enregistrements sont parfaitement dnis. Le cas des enregistrements spars par une ou plusieurs lignes blanches (variable RS vide) est beaucoup plus rare, je nen ai jamais rencontr dans les scripts courants.

Les champs
Lorsque linterprteur awk reoit un enregistrement, il le dcoupe automatiquement en champs. Lenregistrement lui-mme est reprsent par la variable spciale $0. Les champs sont disponibles dans les variables $1, $2, $3, et ainsi de suite jusquau dernier. Le nombre de champs dtects est inscrit dans la variable NF, il est ainsi possible daccder directement au dernier champ avec lexpression $NF, lavant-dernier avec $(NF-1), etc. Par dfaut, les champs sont spars par des caractres blancs (espaces ou tabulations), mais cela peut tre modi par lintermdiaire de la variable FS (Fields Separator), avec les conventions suivantes : lorsque FS est vide, chaque caractre est considr comme un champ indpendant ; $1 contiendra le premier caractre, $2 le suivant, et ainsi de suite ;

Awk CHAPITRE 9

221

lorsque FS contient un seul caractre, il est considr comme tant un sparateur de champs ; lorsque FS contient plusieurs caractres, il sagit dune expression rationnelle qui dcrit la sparation. Utilisons par exemple un chier de type /etc/passwd:
$ cat passwd.txt root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin: daemon:x:2:2:daemon:/sbin: adm:x:3:4:adm:/var/adm: lp:x:4:7:lp:/var/spool/lpd: mail:x:8:12:mail:/var/spool/mail: news:x:9:13:news:/var/spool/news: uucp:x:10:14:uucp:/var/spool/uucp: operator:x:11:0:operator:/root: games:x:12:100:games:/usr/games: ftp:x:14:50:FTP User:/home/ftp: [...] $

Les enregistrements sont spars par des sauts de ligne, ce qui correspond au fonctionnement par dfaut. Nous ne devrons pas modier RS. En revanche, les sparations de champs se font sur des caractres :. Nous pouvons donc examiner les champs avec un script comme :
$ awk 'BEGIN{FS=":"} {print "nom=" $1 " uid=" $3 " home=" $6 }' passwd.txt nom=root uid=0 home=/root nom=bin uid=1 home=/bin nom=daemon uid=2 home=/sbin nom=adm uid=3 home=/var/adm nom=lp uid=4 home=/var/spool/lpd nom=mail uid=8 home=/var/spool/mail nom=news uid=9 home=/var/spool/news nom=uucp uid=10 home=/var/spool/uucp nom=operator uid=11 home=/root nom=games uid=12 home=/usr/games nom=ftp uid=14 home=/home/ftp $

222

Shells Linux et Unix par la pratique

Les variables $1, $2, $NF contiennent les diffrents champs, mais il faut savoir que la gestion de la variable NF par linterprteur est particulirement intelligente. Plusieurs points doivent tre nots : la modication des variables $1 $NF agit sur le contenu de la variable $0 qui reprsente lensemble de lenregistrement ; la consultation dun champ dindice suprieur NF renvoie une chane vide sans autres effets de bord ; lcriture dans un champ dindice suprieur NF augmente cette dernire variable en consquence et modie $0; laugmentation de la valeur de NF na pas deffet direct, les champs supplmentaires contenant des chanes vides comme lorsquon accde aux champs dindice suprieur NF; la diminution de la valeur de NF dtruit les champs concerns et modie la variable $0. Lorsque le nombre de champs est modi, la variable $0 est mise jour en consquence, et le contenu de la variable spciale OFS (Output Field Separator) est utilis pour y sparer les champs. Si on veut contraindre awk reconstituer la variable $0 avec les sparateurs OFS, il suft dcrire dans NF, mme sans en modier le contenu :
$ awk 'BEGIN {FS=":"; OFS="#"} {NF=NF; print $0}' passwd.txt root#x#0#0#root#/root#/bin/bash bin#x#1#1#bin#/bin# daemon#x#2#2#daemon#/sbin# adm#x#3#4#adm#/var/adm# lp#x#4#7#lp#/var/spool/lpd# mail#x#8#12#mail#/var/spool/mail# news#x#9#13#news#/var/spool/news# uucp#x#10#14#uucp#/var/spool/uucp# operator#x#11#0#operator#/root# games#x#12#100#games#/usr/games# [...] $

De mme, le contenu de la variable ORS (Output Record Separator) est automatiquement ajout pour indiquer la n dun enregistrement. Par dfaut, il sagit du saut de ligne, mais nous pouvons le modier :
$ awk 'BEGIN {ORS="#"} {print}' passwd.txt root:x:0:0:root:/root:/bin/bash#bin:x:1:1:bin:/bin:#daemon:x:2:2:daemon:/ sbin:#adm:x:3:4:adm:/var/adm:#lp:x:4:7:lp:/var/spool/lpd:#mail:x:8:12:mail:/var/ spool/mail:#news:x:9:13:news:/var/spool/news:#uucp:x:10:14:uucp:/var/spool/ uucp:#operator:x:11:0:operator:/root:#games:x:12:100:games:/usr/games:#[...]$

Awk CHAPITRE 9

223

Dans nos prcdents scripts, nous avons cr une variable pour compter les enregistrements lus. En ralit, linterprteur awk tient jour automatiquement ce compte dans une variable spciale nomme NR (Number of Records) :
$ awk '{print NR " : " $0}' passwd.txt 1 : root:x:0:0:root:/root:/bin/bash 2 : bin:x:1:1:bin:/bin: 3 : daemon:x:2:2:daemon:/sbin: 4 : adm:x:3:4:adm:/var/adm: 5 : lp:x:4:7:lp:/var/spool/lpd: 6 : mail:x:8:12:mail:/var/spool/mail: 7 : news:x:9:13:news:/var/spool/news: 8 : uucp:x:10:14:uucp:/var/spool/uucp: 9 : operator:x:11:0:operator:/root: 10 : games:x:12:100:games:/usr/games: [...] $

Cette variable est galement accessible en criture, ce qui nous permet par exemple de la dcrmenter lorsque nous rencontrons un enregistrement vide (ici, une ligne blanche ou une ligne de commentaire commenant par un caractre #) :
$ awk '{print NR " : " $1}' < /etc/services 1 : # 2 : # 3 : # 4 : # 5 : # 6 : # 7 : # 8 : # 9 : # 10 : # 11 : 12 : tcpmux 13 : echo 14 : echo [...]

224

Shells Linux et Unix par la pratique

308 : tfido 309 : fido 310 : fido 311 : 312 : # 313 : 314 : linuxconf 315 : $ awk '/(^$)|(^#)/{NR --; next} {print NR " : " $1}' < /etc/services 1 : tcpmux 2 : echo 3 : echo 4 : discard 5 : discard [...] 254 : tfido 255 : tfido 256 : fido 257 : fido 258 : linuxconf $

Lorsque plusieurs chiers sont examins la suite les uns des autres, linterprteur awk renseigne la variable FILENAME avec le nom du chier en cours et la variable FNR avec le numro denregistrement au sein du chier courant (la variable NR nest pas rinitialise lors du passage au chier suivant). Si la lecture des enregistrements se fait depuis lentre standard, la variable FILENAME contient un tiret - :
$ awk '(FNR == 1){ print FILENAME }' /etc/passwd /etc/passwd $ awk '(FNR == 1){ print FILENAME }' /etc/passwd /etc/inittab /etc/passwd /etc/inittab $ ls | awk '(FNR == 1){ print FILENAME }' $

Structures de contrle
Le langage Awk permet dutiliser des structures de contrle varies, mais elles sont trs peu utilises dans son emploi habituel. Aussi ne les dtaillerons-nous pas. Pour plus dinformations on pourra se reporter la page de manuel de awk.

Awk CHAPITRE 9

225

Tests
if (condition) { action } else { action }

Boucles for
for (initialisation; test; iteration) { action }

Boucles while
while(condition) { action }

Fonctions
function nom (arguments) { action }

Tableaux

Non seulement Awk fournit des variables de type chane de caractres et des variables entires, mais il propose galement des tableaux associatifs. Les lments de ces derniers sont indexs par des chanes de caractres plutt que par des indices entiers, comme on la rencontr avec le shell. La dnition et la consultation dun lment dun tableau se font simplement en indiquant lindice entre crochets. Ainsi peut-on crire :
$ awk 'BEGIN{t[1]="un"; t[2]="deux"; print t[1] ", " t[2]}' un, deux $

Lindice tant en ralit une chane de caractres, on peut aussi crire :


$ awk 'BEGIN{t["un"]=1; t["deux"]=2; print t["un"] ", " t["deux"]}' 1, 2 $

Les possibilits offertes par les tableaux associatifs sont trs nombreuses.

226

Shells Linux et Unix par la pratique

Expressions
Nous navons pas encore dtaill les expressions comprises par le langage Awk. Elles sont construites au moyen doprateurs qui ont une syntaxe trs proche de celle du langage C. On les trouvera dans la table suivante, par ordre croissant de prsance :
Oprateur Nom
valeur conditionnelle OU logique ET logique Egalit Ingalit Infriorit Supriorit infriorit stricte supriorit stricte addition soustraction multiplication division modulo opposition ngation logique exponentiation post-incrmentation pr-incrmentation post-dcrmentation pr-dcrmentation

Signication
renvoie val_1 si la condition cond est vrie, et val_2 sinon renvoie une valeur vraie si a ou si b sont non nulles renvoie une valeur vraie si a est non nulle ainsi que b vraie si a est gale b vraie si a est diffrente de b vraie si a est infrieure b vraie si a est suprieure b vraie si a est strictement infrieure b vraie si a est strictement suprieure b renvoie la somme de a et de b renvoie la diffrence entre a et b renvoie le produit de a et de b renvoie le quotient de a par b renvoie le reste de la division entire de a par b renvoie le nombre de signe oppos et de mme valeur absolue que a renvoie la ngation logique de a renvoie a leve la puissance b renvoie la valeur de a, puis lincrmente si cest une variable incrmente a si cest une variable, puis en renvoie la valeur renvoie la valeur de a, puis la dcrmente si cest une variable dcrmente a si cest une variable, puis en renvoie la valeur

cond ? val_1 : val_2 a || b a && b a == b a != b a <= b a >= b a < b a > b a + b a - b a * b a / b a % b -a ! a a ^ b a ++ ++ a a --- a

Les expressions peuvent tre groupes entre parenthses pour rsoudre les problmes de priorits des oprateurs. Les expressions logiques sont fausses si elles sont nulles et vraies sinon.

Awk CHAPITRE 9

227

Lors dune affectation de variable, il est possible dutiliser les raccourcis suivants :
Raccourcis Dveloppement

a += expr a -= expr a *= expr a /= expr a %= expr a ^= expr

a = a + expr a = a - expr a = a * expr a = a / expr a = a % expr a = a ^ expr

On notera galement que les chanes de caractres disposent dun oprateur de concatnation implicite : une expression qui contient deux chanes spares par une ou plusieurs espaces sera value en regroupant automatiquement les deux chanes en une seule :
$ awk 'BEGIN{var="uiop" ; print "aze" "rty" var "qsdf"}' azertyuiopqsdf $

Retour sur les afchages


Nous avons jusqu prsent essentiellement utilis la fonction print. Celle-ci value ses arguments et envoie le rsultat sur la sortie standard, en ajoutant le caractre ORS qui sert sparer les enregistrements en sortie (en principe, il sagit du saut de ligne). Il en existe toutefois une version plus performante, qui permet un afchage format : printf. Celle-ci prend en premier argument une chane de caractres qui dcrit le format utiliser pour prsenter les donnes. Ensuite viennent les variables afcher suivant le format indiqu. Nous avons dj mentionn lexistence dune implmentation de cette routine sous forme dutilitaire shell. La chane de format dcrit les conversions appliquer aux arguments avant de les afcher. Voici quelles sont les conversions possibles :
Conversion Signication
Afchage du caractre dont le code ASCII est fourni en argument. Les valeurs sont automatiquement ramenes dans lintervalle [0, 255] par une opration de modulo. Afchage du nombre sous forme entire en dcimal. Afchage du nombre sous forme relle, avec une mantisse et un exposant : 1.23456e+78. Comme %e, avec une lettre E la place du e (1.23456E+78). Afchage du nombre sous forme relle, sans exposant 123456789.0123. Afchage, sous forme %e ou %f, optimis suivant la taille du chiffre afcher.

%c %d %e %E %f %g

228

Shells Linux et Unix par la pratique

Conversion

Signication
Comme %g en invoquant %E la place de%e. Comme %d. Afchage du nombre sous forme entire, non signe, en octal. Afchage dune chane de caractres. Afchage du nombre sous forme entire, non signe, en dcimal. Afchage du nombre sous forme entire, non signe, en hexadcimal. Comme %x, avec les lettres ABCDEF la place de abcdef .

%G %i %o %s %u %x %X

De plus, on notera que la chane de format peut contenir les squences suivantes, qui reprsentent des caractres spciaux :
Squence Caractre Code ASCII
37 92 nnn (octal) xxx (hxa) Bell Backspace Formfeed Newline Carriage ret. H tab V tab 7 8 12 10 13 9 11 Le caractre dont le code ASCII vaut nnn en octal (exemple \041 pour les guillemets droits) Le caractre dont le code ASCII vaut xxx en hexadcimal (exemple \x01B pour le caractre Escape) Avertisseur sonore Retour en arrire avec effacement Saut de page Saut de ligne Retour chariot Tabulation horizontale Tabulation verticale

Signication

%% \\ \nnn \xnnn \a \b \f \n \r \t \v

% \

Voyons des exemples de conversions, en commenant par celles avec le caractre %c:
$ awk '{printf "%c\n", $0}' 65 A 97 a 250 (Contrle-D) $

Awk CHAPITRE 9

229

Les conversions en nombre entier :


$ awk '{printf "%d\n", $0}' 12 12 13.5 13 -15.7 -15 A 0 (Contrle-D) $

On peut observer les diffrences dafchage en dcimal, octal et hexadcimal. On notera que les prsentations %u, %o et %X convertissent le nombre en valeur non signe :
$ awk '{printf "%d %u %o %X\n", $0, $0, $0, $0}' 12.3 12 12 14 C 65 65 65 101 41 -1 -1 4294967295 37777777777 FFFFFFFF (Contrle-D) $

Voici les afchages sous forme relle :


$ awk '{printf "%f %e %g\n", $0, $0, $0}' AAA 0.000000 0.000000e+00 0 1.234 1.234000 1.234000e+00 1.234 12345 12345.000000 1.234500e+04 12345 1.234e70 123399999999999995924167152120652318521993671372752748551664 59462549504.000000 1.234000e+70 1.234e+70 (Contrle-D) $

230

Shells Linux et Unix par la pratique

Lafchage des chanes se fait ainsi :


$ awk '{printf ">> %s <<\n", $0}' azert yuiop >> azert yuiop << 12 >> 12 << (Contrle-D) $

Entre le signe % et le caractre qui dcrit la conversion peuvent se trouver plusieurs lments. Tout dabord, on peut rencontrer un ou plusieurs caractres parmi les suivants :
Caractre Signication
Justication gauche. Cela concerne essentiellement les valeurs numriques. Afcher un espace devant les nombres positifs, et un signe - devant les nombres ngatifs. Afcher un signe + devant les nombres positifs et un signe - devant les nombres ngatifs. Modier le format : afcher un prxe 0 avec %o, 0x avec %x, 0X avec %X. Laisser les zros non signicatifs avec %g et %G. Toujours afcher le point dcimal pour %e, %E et%f. Complter le champ avec des zros plutt que des espaces pour respecter la largeur demande (voir ci-aprs).

(espace) + # 0

Voici quelques exemples :


$ awk '{printf "%d %+d % d \n", $0, $0, $0}' 12 12 +12 12 -12 -12 -12 -12 (Contrle-D)$ awk '{printf "%#x %#f %#g \n", $0, $0, $0}' 12 0xc 12.000000 12.0000 123456 0x1e240 123456.000000 123456. (Contrle-D) $

Ensuite, on peut fournir une valeur qui indique la largeur minimale du champ. Ce dernier sera complt avec des espaces et des zros :

Awk CHAPITRE 9

231

$ awk '{printf "%5d %5s %5f\n", $0, $0, $0}' 12 12 12 12.000000 (Contrle-D) $ awk '{printf "%05d %05s %05f\n", $0, $0, $0}' 12 00012 00012 12.000000 (Contrle-D) $

tonnamment, le remplissage par des zros la place des espaces concerne aussi les afchages de chanes de caractres :
$ awk '{printf "%018s\n", $0}' hello, world! 00000hello, world! (Contrle-D) $

Enn, on peut trouver une valeur de prcision, prcde dun point. Cette valeur a des signications diffrentes en fonction de la conversion demande : pour %d, %i, %o, %u, %x et %X, il sagit du nombre minimal de chiffres afcher. Cette valeur nest pas toujours redondante avec la largeur du champ car elle ne compte pas le caractre correspondant au signe ; pour %e, %E et %f, il sagit du nombre de dcimales ; pour %g et %G, il sagit du nombre total de chiffres signicatifs ; pour %s, cette valeur reprsente le nombre maximal de caractres crits ; les caractres suivants de la chane seront ignors.
$ awk '{printf "%.8s\n", $0}' azertyuiop azertyui qsdfghjklmwxcvbn qsdfghjk (Contrle-D) $

La fonction printf est donc sensiblement plus puissante que print. On notera que printf najoute pas systmatiquement le caractre ORS en n de ligne.

232

Shells Linux et Unix par la pratique

Il en existe une variante, nomme sprintf, qui nafche pas le rsultat sur la sortie standard, mais le renvoie dans une chane de caractres. Ce comportement est trs prcieux pour construire des chanes prcises, car loprateur de concatnation implicite ne permet pas de limiter le format. On peut par exemple limiter le nombre de caractres dans une ligne de saisie :
$ awk '{saisie = sprintf ("%.8s", $0); print ">>" saisie "<<"}' azertyuiop >>azertyui<< (Contrle-D) $

Conclusion
Nous avons dcouvert dans ce chapitre les fonctionnalits de Awk utilises dans les scripts courants. Toutefois, on notera bien que ce langage propose des fonctions internes bien plus compltes que les simples print et printf dont nous nous sommes contents. Le lecteur intress pourra se reporter la bibliographie ou la page de manuel de awk pour plus de dtails.

Exercices
En premier lieu, je vous propose dessayer de manipuler des chiers de texte, comme /etc/services ou /etc/passwd pour afcher certains champs aprs avoir slectionn des lignes particulires. Nous avons dj ralis des manipulations de ce type dans le chapitre et je vous laisserai le soin de vous y reporter pour trouver la solution de vos expriences. Ensuite je vous conseille de vous familiariser avec lutilisation de awk pour traiter le rsultat de commandes systme (ps, ls, vmstat, free, df, etc.). Donnez-vous un but, par exemple : calculer le pourcentage cumul dutilisation du processeur par tous les processus dun utilisateur et essayez de rsoudre ce problme. Ici nous pourrions faire : appeler ps avec des options pour avoir tous les processus et les afchages concernant lutilisation du CPU (psaux sous Linux) ; utiliser un pipeline pour envoyer le rsultat dans awk; slectionner les lignes concernant lutilisateur choisi, en ltrant sur le contenu dun champ ; ajouter le contenu du champ dutilisation du CPU une variable somme; dans une clause END, afchez le contenu de la variable somme. Rptez rgulirement ce genre dexercice pour tre parfaitement laise avec lutilisation de Awk dans ces conditions.

10
Bonne criture dun script
La syntaxe des scripts shell est relativement complexe, le langage est contraignant et peu tolrant. Il y a un nombre important de rgles respecter (par exemple toujours placer une espace autour des symboles de test [ et ], mais jamais autour du signe =). En outre, lutilisation rptition de certains caractres spciaux ($, {}, &, etc.) pour des usages diffrents rend les scripts intrinsquement difciles lire. La rgle dor pour amliorer la qualit dun script sera donc de toujours privilgier la lisibilit du script. Les autres considrations telles que la rapidit dexcution, loccupation de la mmoire, ou la compacit du code ne concernent que trs peu le shell. En amliorant la lisibilit dun script, on augmente la probabilit de pouvoir en rutiliser des portions dans dautres scripts, et on facilite grandement les oprations de maintenance ultrieures. Les conseils mentionns dans ce court chapitre sont avant tout des rgles de logique, allant dans ce sens.

Prsentation gnrale
Ligne shebang

Pensez insrer la ligne shebang en dbut de script pour indiquer le nom de linterprteur employer. En gnral on appellera :
#! /bin/sh

Toutefois, sur certains systmes (Solaris par exemple), o le shell /bin/sh naccepte pas les syntaxes modernes, on invoquera plutt :
#! /bin/ksh

234

Shells Linux et Unix par la pratique

En-tte du chier

Pour faciliter la maintenance dun script, il est intressant dinsrer quelques lignes de commentaire formant un en-tte, contenant : Le nom du script (ceci peut tre utile si on imprime le listing du programme) :
############################################# # de_double.sh #

Quelques lignes dcrivant le rle et lutilisation du script :


# limine les doublons de la base de donnes, # le fichier initial est toujours sauvegard. # Des options configurent le comportement. #

Le nom de lauteur et son adresse e-mail, surtout si le programme est diffus sur plusieurs plates-formes :
# Auteur : Prenom NOM <prenom.nom@hote.com> #

Un historique des changements : une variable VERSION sera mise jour chaque intervention sur le script, et une brve description de la modication sera incluse dans une liste (classe par ordre chronologique inverse) permettant de voir immdiatement les dernires oprations de maintenance.
# Historique : # 18/01/2005 (1.3) : # correction bug sur longueur des champs # 14/01/2005 (1.2) : # ajout option -f # 09/01/2005 (1.1) : # inversion de l'option -i # 07/01/2005 (1.0) : # criture initiale ############################################# # VERSION=1.3

Commentaires

Les commentaires sont indispensables pour assurer une lisibilit correcte dun script shell. La syntaxe du shell est parfois ardue et les lignes de commande incluant des appels grep, sed ou awk se rvlent souvent de vritables ds pour le lecteur, mme expriment. Il est donc important de bien commenter un script, cest--dire de commenter le but, le rle dun ensemble de lignes, et de ne surtout pas paraphraser le code. Soignez vritablement la rdaction des commentaires (en rdigeant des phrases courtes mais signicatives, de prfrence sans approximations, sans abrviations excessives ni fautes dorthographe systmatiques) et vous simplierez nettement la relecture ultrieure et les oprations de maintenance sur votre programme.

Bonne criture dun script CHAPITRE 10

235

Indentation

Le fait dindenter correctement un script nest pas intuitif. Les programmeurs dbutants ont parfois du mal savoir comment dcaler leurs lignes de code de la marge. Il sagit pourtant dune trs bonne habitude prendre, qui devient vite un automatisme et amliore la lisibilit du script. Lorganisation logique du script doit apparatre au premier regard ; le dbut et la n de chaque structure de contrle (boucle, test, fonction...) tant mis en relief par le dcalage du code contenu dans la structure.

Les variables
Noms des variables

Pour nommer les variables, je conseille gnralement de suivre une rgle assez simple : Pour les variables qui ne sont utilises que dans une petite portion de code (ne dpassant gure un cran dditeur de texte), prfrer les noms courts comme i, j, fic, arg, etc. Pour les variables qui sont utilises plusieurs reprises dans des emplacements loigns au sein du script, utiliser des noms signicatifs et complets (par exemple fichier_entree, repertoire_installation, etc.) Par convention, les variables que lon initialise au dbut du script, et qui conservent une valeur constante, par la suite, pendant toute son excution, sont crites avec des majuscules (VERSION par exemple).
Utilisation des variables

Lors de la consultation du contenu dune variable provenant de lextrieur du script (argument de la ligne de commande, saisie dun utilisateur, contenu dun chier, liste des chiers dun rpertoire, etc.), pensez toujours encadrer le nom de la variable par des guillemets. Ils garantissent que le contenu de la variable conserve son intgrit, mme sil sy trouve des espaces. En dbutant un script par loption set -u, on empche la consultation dune variable non initialise. Ceci permet de dtecter certaines fautes de frappe.
Variables des fonctions

Nous lavons dj mentionn, les variables utilises dans les fonctions sont globales par dfaut, et seul lemploi du mot-cl local permet de restreindre leur porte. Il faut prendre lhabitude de dclarer systmatiquement toutes les variables avec ce motcl en dbut de fonction (sauf cas particulier o lon dsire vraiment utiliser une variable commune au reste du script). Je cite souvent lexemple dun script o une variable rep

236

Shells Linux et Unix par la pratique

tait utilise pour stocker un nom de rpertoire, et o une fonction appele occasionnellement dans la partie du script utilisant ce rpertoire employait une variable rep pour enregistrer la rponse de lutilisateur une question. La collision entre ces deux variables fut facilement limine en utilisant local, mais aprs une longue recherche pour trouver lerreur.

Gestion des erreurs


Arguments en ligne de commande

Un bon script doit adopter un comportement souple, paramtrable par son utilisateur. Par exemple on laissera celui-ci choisir : les rpertoires contenant les donnes traiter ; les chiers de conguration utiliser ; le degr de volubilit du script, avec au moins un mode silencieux pour un lancement en arrire-plan, et un mode bavard dtaillant les oprations en cours, etc. Pour cela on utilisera des options et des arguments en ligne de commande, traits par exemple par getopts. Il sera important de vrier le nombre et la validit des arguments ds le dbut du script, avant dentamer tout travail. Pour aider lutilisateur retrouver la signication des options du script, on afchera un message (par convention, en rponse loption -h ou --help) rappelant leur syntaxe et leur utilisation. Dans le cas o lutilisateur na pas fourni une option indispensable (un rpertoire de travail par exemple), on la remplacera par le contenu dune variable denvironnement choisie avec soin et documente dans laide du script (WORKDIR par exemple). Si cette variable nest nalement pas dnie, le script pourra se rabattre sur une valeur par dfaut prdnie (par exemple ~/tmp).
Codes de retour

Pour assurer la robustesse dun script, il est indispensable de vrier le code de retour de toutes les commandes excutes. En effet mme des commandes a priori videntes, comme se placer (avec cd) dans un rpertoire que lon a dj examin, peuvent chouer : supposez par exemple que ce rpertoire se trouve sur un support amovible cl ou disque USB que lutilisateur vient dextraire abruptement ; il faudra arrter proprement lexcution du script, ou demander lutilisateur de rinsrer le support. On sait que chaque commande renvoie un code indiquant ses conditions de terminaison dans la variable $?. Il serait donc possible de vrier systmatiquement cette variable par un test :
if [ $? -ne 0 ]; then ... fi

Bonne criture dun script CHAPITRE 10

237

Toutefois cela serait vite fastidieux, aussi je vous conseille plutt dutiliser la construction || dj vue dans le chapitre 4. On pourrait ainsi tester le bon droulement dun script dinstallation, par exemple :
... echo "Insrez le CD dinstallation puis pressez Entre" >&2 read || exit 1 mount /mnt/cdrom || { echo "CD absent" >&2; exit 1; } cd ${INSTALL_DIR} || { echo " $INSTALL_DIR interdit" >&2; exit 2;} tar xf /mnt/cdrom/install.tar || { echo "Archive illisible" >&2; exit 3; } ...

Cela prsente plusieurs avantages : tous les appels des commandes systme sont vris et seul un code de retour nul, signiant russite , permet la continuation du script ; on repousse la gestion des erreurs sur la partie droite du listing, en gardant le l dexcution normal en partie gauche, sans tre frein dans la lecture par des lignes de gestion derreur (une ligne dun script peut tre beaucoup plus large que sur cette page, et le message derreur plus dtaill que ci-dessus) ; on fournit en sortie (avec la commande exit) un code de retour signicatif dcrire dans la documentation du script , que lon pourrait tester dans la variable $? du shell, une fois lexcution termine.
Messages derreur

Les messages derreur doivent tre signicatifs. Certaines applications se terminent parfois avec un Erreur ; ne peut pas continuer ! laconique. Ceci est trs frustrant pour lutilisateur qui na aucune information sur le problme rencontr. Au contraire, un bon message derreur donnera lutilisateur des pistes pour corriger le dfaut. cet gard, les messages prsents dans les lignes ci-dessus ne sont vraiment pas trs bons, mais la largeur de la page du livre nous limite pour cet exemple. Il est important, au moment de rdiger un message derreur, de se placer dans la peau dun utilisateur qui naura que cette information pour essayer de pallier le problme. En rsum : un message derreur doit tre un guide pour rsoudre le problme, et non pas une justication pour abandonner le travail du script. On enverra tous les messages interactifs vers la sortie derreur, avec la redirection >&2 pour lafchage.

238

Shells Linux et Unix par la pratique

Messages de dbogage

Il existe une autre catgorie de messages interactifs, qui peuvent tre beaucoup plus concis que les messages derreur : les messages utiliss pour le dbogage pendant la mise au point du script. Il est trs utile de glisser de temps autre des points dinformation sur ltat de certaines variables, le rsultat des commandes, etc. On notera que si le message doit tre envoy sur le terminal, quelles que soient les redirections qui ont pu tre appliques au lancement du script, on peut employer la notation >/dev/tty sur la ligne de commande de echo. Une fois la mise au point termine, on pourrait tre tent de retirer les messages de dbogage avant la livraison du script ses utilisateurs naux. Toutefois je conseille plutt dutiliser une mthode diffrente, et dinvalider simplement les messages de dbogage en les laissant dans le script. Le jour o une volution sera demande, il sera toujours possible de rutiliser ces messages, plutt que den crire de nouveaux. Pour cela, plusieurs approches sont possibles : Mettre les messages en commentaires, en les faisant prcder dun dise #. Ceci prsente linconvnient de devoir diter chaque ligne de dbogage, aussi bien pour la masquer que pour la rintgrer ultrieurement. La deuxime approche consiste initialiser, en dbut de script, une variable DEBUG en la remplissant avec true ou false. Noublions pas que ce sont deux commandes qui renvoient respectivement des codes de retour Vrai et Faux. Ensuite on fera prcder le echo du message dun dbogage par $DEBUG&& ainsi :
$DEBUG && echo " Variable i vaut $i" >/dev/tty

Lorsque DEBUG sera initialise avec true, son valuation renverra une valeur vraie, et la construction && demandera lexcution de la seconde partie de la ligne, cest--dire lafchage du message. Symtriquement, quand DEBUG sera remplie avec false, lvaluation renvoyant Faux, la seconde partie sera ignore et le script continuera sans afcher le message. La troisime possibilit est une version afne de la prcdente : on va remplir une variable DEBUG_LEVEL avec une valeur numrique positive ou nulle, et le test employ dans les lignes de dbogage sera :
[ $DEBUG_LEVEL gt 0 ] && echo " Variable i vaut $i" > /dev/tty

Si DEBUG_LEVEL contient zro, le test choue et le message napparat pas. Si DEBUG_LEVEL est suprieur zro, le message est afch. Lavantage de cette mthode est la possibilit de grer plusieurs niveaux de dbogage, certains messages napparaissant que si le niveau est suprieur 2, dautres 5, 10 au choix du programmeur.

Bonne criture dun script CHAPITRE 10

239

Les fonctions
Les fonctions amliorent souvent la qualit dun script, car elles constituent des composants indpendants que lon pourra rutiliser plus facilement dans dautres scripts, ultrieurement.
En-tte

Je conseille de dbuter une fonction par un commentaire indiquant le rle de la fonction, les arguments quelle attend, ce quelle afche sur sa sortie standard et les codes de terminaison quelle renvoie. Ceci est dautant plus important si la mme fonction doit tre employe dans plusieurs scripts, comme nous le verrons ci-dessous.
Variables

Les variables des fonctions tant globales par dfaut, rappelons nouveau quil faut toujours les dclarer avec le mot-cl local pour viter les confusions avec le reste du script.
Bibliothques

Un aspect vraiment intressant des fonctions est la possibilit de les regrouper dans des chiers bibliothques, o elles seront disponibles pour plusieurs scripts. Voyons un exemple. Jcris une fonction de saisie blinde qui afche une question lutilisateur (le libell de la question est transmis la fonction, en argument), et qui attend une rponse O ou N. Elle renverra un code de terminaison Vrai si lutilisateur rpond O, et Faux sil saisit N.
1 function repondre_oui_ou_non 2 { 3 # Cette fonction pose l'utilisateur la question passe 4 # en argument et attend en rponse 'O' ou 'N'. Le code de 5 # retour est Vrai pour une rponse 'O' et Faux pour 'N'. 6 local reponse 7 while true 8 do 9 echo "$@ (O/N)" >&2 10 read reponse 11 if [ "$reponse" = "O" ]; then return 0; fi 12 if [ "$reponse" = "N" ]; then return 1; fi 13 done 14 }

Cette fonction peut mtre utile dans plusieurs scripts, aussi vais-je lenregistrer dans un chier o je regrouperai une collection de routines utilitaires comme celle-ci : biblio.sh.

240

Shells Linux et Unix par la pratique

Lorsquun script a besoin daccder cette fonction, il va sourcer la bibliothque en employant le symbole point . ainsi :
script.sh: 1 2 3 4 5 6 7 8 9 10 11 12 #! /bin/sh . ./biblio.sh if repondre_oui_ou_non "Sauvegarder avant de quitter ?" then echo "Sauvegarde en cours..." sleep 2 echo "Sauvegarde Ok" else echo "Pas de sauvegarde" fi

Ici nous avons indiqu explicitement le rpertoire o se trouve la bibliothque en utilisant la notation ./, mais si elle se trouvait dans un rpertoire mentionn dans la variable denvironnement PATH, on pourrait donner uniquement son nom.
$ ./script.sh Sauvegarder avant de quitter ? (O/N) O Sauvegarde en cours... Sauvegarde Ok $ ./script.sh Sauvegarder avant de quitter ? (O/N) non Sauvegarder avant de quitter ? (O/N) N Pas de sauvegarde $

Notez que le partage de fonctions entre scripts, par lintermdiaire de bibliothques, permet de disposer immdiatement, dans tous les scripts, des corrections et amliorations apportes une fonction. Le mme principe peut tre employ pour initialiser des constantes avec des valeurs utilises dans plusieurs scripts sappliquant un projet commun.

Conclusion
Nous avons observ dans ce chapitre quelques rgles de bon usage des scripts. La motivation essentielle du programmeur consciencieux doit tre la lisibilit de ses scripts ; viendra immdiatement ensuite leur robustesse ; et enn la gnralit an de permettre la rutilisation ultrieure du code dans dautres scripts.

Bonne criture dun script CHAPITRE 10

241

Exercice
En guise dexercice je vous propose de regarder une srie de scripts, une bote outils dans laquelle vous pourrez trouver des structures, des fonctions, des applications trs diverses susceptibles dtre rutilises ou, du moins, de servir de source dinspiration. Ces scripts peuvent tre tlchargs avec les exercices des autres chapitres. En voici une liste non exhaustive, car jajouterai dautres scripts rgulirement : affiche_dup.sh: recherche les chiers identiques dans une liste fournie en argument ; barre_progression.sh: afche un indicateur (en pourcentage) de la progression dune tche, dune installation, dun traitement ftpauto.sh: automatisation dun transfert de chier en utilisant la commande ftp habituellement interactive ; kill_user.sh: tue tous les processus de lutilisateur indiqu ; liste_ancien_fichiers.sh: liste les chiers selon leur anciennet (paramtrable) ; option_shell.sh: analyse les options du shell en cours et les prsente en clair ; pause.sh: marque une pause de dix secondes maximum pendant lesquelles lutilisateur peut conrmer la continuation du traitement ; renomme_fichiers.sh: remplace une chane par une autre dans le nom des chiers indiqus la suite ; squel_traite_fichiers_argument.sh: squelette pour traiter les chiers indiqus en argument sur la ligne de commande ; squel_traite_fichiers_entree.sh: squelette pour traiter les chiers indiqus sur lentre standard du script ; squel_traite_fichiers_argument.sh: squelette pour traiter les chiers lists dans un chier indiqu en argument ; supervision.sh: aprs installation, permet de passer des commandes ou de tlcharger des chiers sur une srie de machines, sans saisir de mot de passe, en sappuyant sur SSH ; timeout.sh: lance une commande avec une dure maximale pour son excution, en la tuant si elle dure plus longtemps ; wait_process.sh: attend que tous les processus indiqus sur la ligne de commande se soient termins ; wipe.sh: efface les chiers indiqus et crase plusieurs reprises leur contenu en y crivant successivement des 0 et des 1.

A
Solutions des exercices
Vous trouverez ci-dessous les commentaires des solutions aux exercices proposs dans ce livre. Les scripts proprement dits pourront tre chargs sur le site de lauteur :
http://www.blaess.fr/christophe

Chapitre 1
1.1 Prise en main du systme
Pour identier les shells disponibles sur votre systme, il existe plusieurs possibilits. Je vous propose la solution suivante : ls /bin/*sh*: nous recherchons tous les chiers contenant la portion sh dans le rpertoire systme principal (/bin). Attention, certains utilitaires systme comme chsh pourront tre mentionns, bien quils ne soient pas des shells. Dterminer lidentit de /bin/sh peut tre plus problmatique. En gnral, il suft toutefois de lister ls-l/bin/sh pour sapercevoir que cest un lien symbolique vers bash ou ksh.

1.2 Utilit de la variable PATH


Aprs avoir vid la variable PATH, nous nous retrouvons dans une situation o seules les commandes internes du shell (cd, echo...) fonctionnent. Lappel ls choue avec un message derreur Commande non trouve . En revanche, si lon prcise le chemin par lequel le shell peut trouver lexcutable ls (comme dans lappel /bin/ls), celui-ci fonctionnera normalement. Pour restaurer la variable PATH, vous pouvez la remplir nouveau en restituant le contenu prcdent, mais il est probablement plus simple de fermer le terminal sur lequel vous travailliez (terminant ainsi le shell) et de relancer une nouvelle session.

244

Shells Linux et Unix par la pratique

1.3 Rpertoire courant dans le PATH


Lappel direct fonctionne ds que le rpertoire courant (point) est dans la variable PATH.

1.4 Dangers associs au PATH


Le premier utilisateur a cr le chier script suivant :
ls #! /bin/sh echo "BOUM ! je viens d'effacer tous mes fichiers"

Ensuite il a rendu le chier excutable et la copi dans /tmp.


$ chmod +x ls $ cp ls /tmp

Le second utilisateur est arriv sur le systme, et a modi sa variable PATH en toute innocence. Puis il sest rendu dans /tmp et en a regard le contenu :
$ PATH=.:$PATH $ cd /tmp $ ls BOUM ! je viens d'effacer tous mes fichiers $

Lorsquil a tap ls, le systme a recherch un chier excutable correspondant en suivant lordre des rpertoires inscrits dans la variable PATH. Le premier ls trouv est /tmp/ls, un script pig crit par un autre utilisateur malintentionn, et que notre victime vient dexcuter par inadvertance. Conclusion : ne jamais mettre le rpertoire courant en dbut de PATH!

Chapitre 2
2.1 Appel dun script par source
Lorsquon appelle notre script avec :
$ ./initialise_et_affiche.sh

un nouveau processus shell est invoqu (grce sa premire ligne shebang) pour interprter les commandes. La variable nest donc remplie que dans la mmoire de ce nouveau shell et, une fois celui-ci termin, le contenu est perdu.

Solutions des exercices ANNEXE A

245

En revanche, avec linvocation :


$ . initialise_et_affiche.sh

cest le shell courant (interactif) qui interprte le chier. Le contenu de la variable est donc toujours visible aprs excution du script.

Chapitre 3
3.1 Majuscules et minuscules dans les chiers
Deux points noter dans les scripts upper_file.sh et lower_file.sh: le parcours les arguments de la ligne de commande pour les traiter successivement ; lutilisation dun chier temporaire cr en ajoutant .tmp la n du nom de chier normal :
upper_file.sh: 1 2 3 4 5 6 7 8 #! /bin/sh for fic in "$@" do tr '[[:lower:]]' '[[:upper:]]' < "$fic" > "${fic}.tmp" mv "${fic}.tmp" "$fic" done

Le script lower_file.sh est identique, seuls les arguments de tr sont inverss.

3.2 Majuscules, minuscules et noms de chiers


Lide consiste ici envoyer le nom du chier sur lentre standard de tr (par un echo) puis rcuprer le nom modi par une construction $(...). La structure globale du script est trs proche de celle de lexercice prcdent :
upper_file_name.sh 1 2 3 4 5 6 7 8 9 10 #! /bin/sh for fic in "$@" do FIC=$(echo "$fic" | tr '[[:lower:]]' '[[:upper:]]') if [ "$fic" != "$FIC" ] then mv "$fic" "$FIC" fi done

246

Shells Linux et Unix par la pratique

3.3 Arithmtique et invocation de commande


Il faut dcomposer le travail en trois tapes : obtention de lanne en cours avec date, calcul de lanne suivante, et appel de cal. On pourrait regrouper les trois tapes sur une seule ligne de commande, mais je prfre privilgier la lisibilit du script :
calendrier.sh 1 2 3 4 5 6 7 8 9 10 11 #! /bin/sh # obtenir l'anne en cours annee=$(date +"%Y") # calculer l'anne suivante suivante=$((annee + 1)) # appeler la commande cal cal $suivante

3.4 Extractions de motifs


Voici un exemple de solution :
dirbasename.sh: 1 2 3 4 5 6 7 8 9 10 #! /bin/sh for arg in "$@" do chemin=${arg%/*}/ fichier=${arg##*/} echo "$arg :" echo " chemin $chemin" echo " fichier $fichier" done

3.5 Contournement des apostrophes


Le script propos ci-dessous utilise la commande printf pour afcher les donnes. Elle permet un afchage format o le retour la ligne est explicitement indiqu par les caractres \n:
auto.sh: 1 2 3 #! /bin/sh function quine { printf "%s" "$@"

Solutions des exercices ANNEXE A

247

4 5 6 7 8 9 10 11 12 13 14 15

printf "\047" printf "%s" " "$@" printf "\047\n" } quine #! /bin/sh function quine () { printf "%s" "$@" printf "\047" printf "%s" "$@" printf "\047\n" } quine

Pour vrier que lexcution donne bien le rsultat attendu, nous redirigerons la sortie du programme vers un chier que nous comparerons avec le script original :
$ ./auto.sh #! /bin/sh function quine () { echo -n "$@" echo -ne "\047" echo -n "$@" echo -e "\047" } quine #! /bin/sh function quine () { echo -n "$@" echo -ne "\047" echo -n "$@" echo -e "\047" } quine $ ./auto.sh > sortie_auto.txt $ diff auto.sh sortie_auto.txt $

Il est certainement possible de crer des scripts qui sont capables dcrire leur propre code source dune manire plus concise et plus lgante (je ne considre pas cat$0 comme plus lgant !), et le lecteur pourra samuser rechercher les moyens de contourner les limitations demploi des apostrophes.

248

Shells Linux et Unix par la pratique

Chapitre 4
4.1 Redirection de lentre standard
En fait, ls ne tient aucun compte de son entre standard, mais afche le contenu du rpertoire courant ou de ceux indiqus sur la ligne de commande. Les redirections </dev, </etc ou </usr/bin sont donc ignores et ls prsente toujours le rpertoire courant.

4.2 Redirection vers chier inaccessible


Le shell met en place les redirections avant de lancer les commandes. Si la redirection est impossible, le lancement de la commande est annul, mme si celle-ci, par ailleurs, ne lit jamais le chier concern.

4.3 Redirection pour une commande inexistante


La tentative dexcution dune commande na lieu quaprs analyse de la ligne complte et mise en place des redirections. Si lexcution choue, le chier vers lequel la sortie standard a t redirige est toutefois cr, mais il est vide.

4.4 Affectation temporaire de variable


Le rsultat des commandes nest pas trs intuitif. La premire remplit la variable VAR avec la valeur 1234 et la seconde afche le rsultat. La troisime ligne modie temporairement le contenu de VAR pour y placer 5678 et afcher le rsultat. La dernire commande afche nouveau le contenu de VAR; comme attendu il a retrouv sa valeur initiale : 1234. En fait, la surprise vient du rsultat de la troisime ligne. On sattendait voir safcher 5678, le contenu temporaire de la variable, mais cest 1234 le contenu initial qui apparat. En effet le shell procde dabord lanalyse de toute la ligne, y compris le remplacement des variables par leur valeur, avant lexcution. Ici, notre variable est bien remplie avec 5678, mais la ligne a dj t modie et la commande excute est echo1234 !

4.5 Structures de boucle for-do-done


numerote_fichiers.sh: 1 2 3 4 5 #! /bin/sh numero=1 for fic in *

Solutions des exercices ANNEXE A

249

6 7 8 9

do echo "$numero) $fic" numero=$((numero + 1)) done

4.6 Structures de boucle while-do-done


boucle.sh: 1 2 3 4 5 6 7 8 9 #! /bin/sh i=1 while [ $i -le 5 ] do echo "$@" sleep 1 i=$((i+1)) done

4.7 Tests des caractristiques dun chier


1 #! /bin/sh 2 3 for i in "$@" ; do 4 echo "$i : " 5 if [ -L "$i" ] ; then echo " (lien symbolique) " ; fi 6 if [ ! -e "$i" ] ; then 7 echo " n'existe pas" 8 continue 9 fi 10 echo -n " type = " 11 [ -b "$i" ] && echo "spcial bloc " 12 [ -c "$i" ] && echo "spcial caractre " 13 [ -d "$i" ] && echo "rpertoire " 14 [ -f "$i" ] && echo "fichier rgulier " 15 [ -p "$i" ] && echo "tube nomm " 16 [ -S "$i" ] && echo "socket " 17 echo -n " accs = " 18 [ -r "$i" ] && echo -n "lecture " 19 [ -w "$i" ] && echo -n "criture " 20 [ -x "$i" ] && echo -n "excution " 21 echo "" 22 [ -G "$i" ] && echo " notre GID" 23 [ -O "$i" ] && echo " notre UID" 24 done

250

Shells Linux et Unix par la pratique

Voici des exemples dexcution sur un systme Linux (le nom des chiers spciaux dans /dev variera sur un autre systme Unix) :
$ ./test_fichier.sh /etc/passwd /etc /etc/passwd : type = fichier rgulier /etc/ : type = rpertoire accs = lecture excution accs = lecture $ ln -sf /etc/passwd lien_local $ ./test_fichier.sh lien_local lien_local : (lien symbolique) type = fichier rgulier accs = lecture $ mkfifo tube $ ./test_fichier.sh tube tube : type = tube nomm accs = lecture criture notre GID notre UID $ ./test_fichier.sh /dev/hda1 /dev/ttyS0 /dev/hda1 : type = spcial bloc accs = /dev/ttyS0 : type = spcial caractre accs = $

Chapitre 5
5.1 Comptage rebours
rebours.sh : 1 #! /bin/sh 2

Solutions des exercices ANNEXE A

251

3 4 5 6 7 8 9

for i in $(seq 5 -1 1) do echo "Reste $i secondes avant l'action" sleep 1 done echo "Action !"

5.2 Cration dun menu


menu.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #! /bin/sh cat <<- FIN Choisissez une option : 1 2 3 4 5 Crer une nouvelle archive Transfrer une archive sur bande Envoyer une archive au serveur Rcuperer une ancienne archive Lister le contenu d'une archive

0 - Quitter FIN while true do echo -n "Votre choix : " read reponse case "$reponse" in "1" ) echo "Prt crer une nouvelle archive" ;; "2" ) echo "Prt transfrer une archive sur bande" ;; "3" ) echo "Prt envoyer une archive au serveur" ;; "4" ) echo "Prt rcuperer une ancienne archive" ;; "5" ) echo "Prt lister le contenu d'une archive" ;; "0" ) echo "Au revoir..." ; exit 0 ;; * ) echo "Option $reponse inconnue" ;; esac done

5.6 Saisie de rponse (complet)


1 2 3 4 5 #! /bin/sh function reponse { local rep

252

Shells Linux et Unix par la pratique

6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

while true do echo -n "$@ (O/N) " >&2 read rep case "$rep" in [OoYy]* ) return 0 ;; [Nn]* ) return 1 ;; esac done } mini=1 maxi=1000 echo "Choisissez un nombre entre 1 et 1000, je vais le deviner." while true do milieu=$(( (mini+maxi) / 2 )) reponse "le nombre est-il superieur ou gal $milieu" if [ $? -eq 0 ] then mini=$milieu else maxi=$milieu fi if [ $mini -eq $maxi ] || [ $mini -eq $((maxi - 1)) ] then echo "Le nombre est $mini" break fi done

Chapitre 7
Expression chane Corresp. ?
oui

Justication
Lexpression demande zro, une ou plusieurs occurrences de la lettre a, donc peut tre mise en correspondance avec un seul exemplaire. Lexpression a* peut correspondre la chane vide. Il y a une chane vide avant et aprs le b. Cette fois, lexpression demande une chane ne contenant, du dbut la n, quune srie de a. Lexpression demande un a en n de chane. Lexpression demande un a en n de chane. La chane se termine par un $. Elle pourrait correspondre une expression comme a\$$.

a*

a* ^a*$ a$ a$

b b a a$

oui non oui non

Solutions des exercices ANNEXE A

253

Expression

chane

Corresp. ?
non non

Justication

a. a+b

a a+b

a doit tre suivi dun caractre. La n de chane nest pas un


caractre (pas plus que le saut de ligne en gnral). Lexpression demande un ou plusieurs caractres a suivis dun caractre b. Le + de la chane est en trop entre eux. Une expression correspondante serait a\+b. Lexpression signie seulement n de ligne , sans autre prcision, et peut donc tre mise en correspondance avec toutes les chanes. La rponse dpend de lutilitaire employ. Avec grep, le symbole $ garde toujours sa signication n de ligne . Le seul $ pris en compte est le premier, et lexpression revient au cas prcdent. Avec sed par exemple le $ ailleurs quen n de chane perd sa signication. Lexpression rclame donc un $ en n de ligne. Pour crire correctement lexpression rationnelle signiant un caractre $ en n de chane , il faut crire \$$.

oui

$$

???

\$$

oui

Cette expression est la bonne criture de la prcdente, interprte par sed et grep de la mme manire (caractre $ en n de ligne). Lexpression demande une chane vide. Celle-ci ne lest pas. Avec sed, les symboles $ et ^ perdent leur signication spciale, car ils ne sont pas aux bons endroits dans lexpression. Cette dernire dcrit donc bien la chane transmise. Avec grep, lexpression indique une n de chane suivie dun dbut de chane, ce qui ne peut correspondre qu une chane vide. Vrai pour sed et grep. Manire correcte dcrire lexpression prcdente. Lexpression demande un caractre quelconque deux reprises... ... mais pas ncessairement deux fois le mme. Lexpression demande exactement deux occurrences... ...du mme caractre ! Lintervalle est invalide dans lexpression rationnelle. Il ne sagit pas dun intervalle, mais dune liste inverse (dbutant par ^) contenant les lments - et _. Le caractre _ tant dans la liste, il est rejet. idem avec -.

^$ $^

^$ $^

non ???

\$\^ ^.{2}$ ^.{2}$ ^(.)\1$ ^(.)\1$ [b-a] [^-_]

$^ aa ab aa ab b _

oui oui oui oui non non non

[^-_] [^-_] []-[] [][-]

^ -

non oui non oui

^ nest pas dans la liste, il est donc accept.


Nous avons un intervalle stendant de ] [ . Vu lordre des caractres Ascii, lintervalle est invalide. Cette fois, nous avons un ensemble contenant [, ] et -. Le signe - est accept.

254

Shells Linux et Unix par la pratique

Chapitre 8
On remarquera, dans ce script, la prsence du "$@" lors de la premire invocation de sed: si on a mis des noms de chiers en arguments sur la ligne de commande, on lit leur contenu (sans les modier) ; sans argument, on travaille avec lentre standard :
latin1_en_html.sh: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #! /bin/sh sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed sed -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e -e 's//\&Agrave;/g' "$@" | 's//\&Acirc;/g' | 's//\&AElig;/g' | 's//\&Ccedil;/g' | 's//\&Egrave;/g' | 's//\&Eacute;/g' | 's//\&Ecirc;/g' | 's//\&Euml;/g' | 's//\&Icirc;/g' | 's//\&Iuml;/g' | 's//\&Ocirc;/g' | 's//\&Ugrave;/g' | 's//\&Ucirc;/g' | 's//\&Uuml;/g' | 's//\&agrave;/g' | 's//\&acirc;/g' | 's//\&aelig;/g' | 's//\&ccedil;/g' | 's//\&egrave;/g' | 's//\&eacute;/g' | 's//\&ecirc;/g' | 's//\&euml;/g' | 's//\&icirc;/g' | 's//\&iuml;/g' | 's//\&ocirc;/g' | 's//\&ugrave;/g' | 's//\&ucirc;/g' | 's//\&uuml;/g' | 's//\&yuml;/g'

B
Bibliographie
Livres et articles
Des copies de tous les articles, disponibles sous forme lectronique, sont regroupes dans une archive propose sur le site Web de lauteur : http://www.blaess.fr/christophe. [BLAESS 1996] Christophe Blaess Securing your rm Linux Gazette numro 8, mai 1996. http://www.linuxgazette.net/issue01to08/articles.html#rm. [BLAESS 2000] Christophe Blaess Programmation systme en C sous Linux ditions Eyrolles, mai 2000. [CHRISTIANSEN 1995] Tom Christiansen CSH Programming Considered Harmful comp.unix.shell, 28/09/1995. http://www.faqs.org/faqs/unix-faq/shell/csh-whynot/. [DOUGHERTY 1990] Dale Dougherty et Arnold Robbins Sed & Awk OReilly & Associates, 1990. [DUBOIS 1995] Paul DuBois Using csh & tcsh OReilly & Associates, 1995. [HAUBEN 1996] Michael et Ronda Hauben Netizens, On the History and Impact of the Net http://www.columbia.edu/~hauben/netbook/ Chapitre 9 : On the Early History and Impact of Unix. [HOFSTADTER 1985] Douglas Hofstadter Gdel, Escher, Bach, les Brins dune Guirlande ternelle InterEditions, 1985. Traduction franaise de Jacqueline Henry et Robert French. [JOY 1978] William Joy An Introduction to the C Shell University of California, Berkeley, 1978. http://www.kitebird.com/csh-tcsh-book/csh-intro.ps.

256

Shells Linux et Unix par la pratique

[KNUTH 1973a] Donald E. Knuth The Art of Computer Programming Fundamental Algorithms volume 1, Addison-Wesley Publishing Company, 1973. [KNUTH 1973b] Donald E. Knuth The Art of Computer Programming Seminumerical Algorithms volume 2, Addison-Wesley Publishing Company, 1973. [KNUTH 1973c] Donald E. Knuth The Art of Computer Programming Sorting and Searching volume 3, Addison-Wesley Publishing Company, 1973. [NEWHAM 1995] Cameron Newham et Bill Rosenblatt Le shell Bash OReilly & Associates, 1995. Traduction franaise de Ren Cougnenc (titre original : Learning the Bash Shell). [ORR 1999] Giles Orr Bash Prompt Howto v0.60. Linux Documentation Project, 1999. [RITCHIE 1974] Dennis M. Ritchie et K. Thompson The UNIX Time-Sharing System Communications of the ACM, volume 17, numro 7, juillet 1974. http://cm.bell-labs.com/cm/ cs/who/dmr/hist.html. [ROSENBLATT 1994] Bill Rosenblatt Learning the Korn Shell OReilly & Associates, 1994.

Sites de rfrence
Norme Single UNIX Specication Version 3
http://www.unix-systems.org/single_unix_specication/:

accs (libre et gratuit) la norme, avec

possibilit de tlchargement complet.

Bash
http://cnswww.cns.cwru.edu/~chet/bash/bashtop.html:

page ofcielle sur le site de Chet Ramey, le

mainteneur de Bash.
http://www.gnu.org/software/bash/bash.html:

page de prsentation et de tlchargement de Bash

sur le site de la FSF.

Korn shell
http://www.kornshell.com/:

page ofcielle du shell Korn. page personnelle de David G. Korn.

http://www.kornshell.com/~dgk/:

Pdksh
http://www.cs.mun.ca/~michael/pdksh/: page ofcielle de Pdksh sur le site personnel de Michael Rendell, son mainteneur.

Bibliographie ANNEXE B

257

Tcsh
http://www.tcsh.org/

Zsh
http://www.zsh.org/:

page ofcielle de tlchargement de Zsh et de sa documentation.

Sed
http://www.gnu.org/software/sed/sed.html:

page de prsentation et de tlchargement de Sed sur

le site de la FSF.
http://www.student.northpark.edu/pemente/sed/: page consacre Sed sur le site personnel dEric Pement, mainteneur de la FAQ associe. http://spazioweb.inwind.it/seders/:

site collectant des scripts Sed plus ou moins utiles.

Index
Symboles ! 108, 208 # 10, 67, 204, 234 #! 10, 16 $ 180, 182, 183 $! 86, 148, 156 $# 64, 121 $$ 24, 133, 148, 156, 165 $( commande ) 47, 92, 125 $(( expression )) 20, 44 $* 64, 65, 66, 71 $? 147, 158, 236 $@ 64, 66, 71, 155 ${#variable} 41 ${variable##motif} 34 ${variable#motif} 34 ${variable%%motif} 34 ${variable%motif} 34 ${variable/motif/remplacement} 37 ${variable:debut:longueur} 33 ${variable:-defaut} 42 ${variable} 32 $0 59, 61, 64, 122, 155, 156 $0 (Awk) 217, 220, 222 $1 20, 59, 121 $1 (Awk) 220, 222 $variable 19, 30, 49, 73, 176 & 13, 81, 83, 86, 155, 156, 160, 208 && 81, 87, 108 ' chane ' 68, 69 ( commandes ) 22, 89 * 34, 68, 180, 184, 186 + 187 . 16, 118, 133, 180, 181, 184 .prole 24 /dev/null 95 /dev/tty 171, 238 /etc/passwd 138, 168 /etc/shadow 138 < 92 <&- 162 << 13, 101 <<- 101 > 13, 93, 134 >&- 162 >&2 93, 100, 237 >> 13, 93 ? 34, 187 [ expression ] 21, 87, 113, 156, 233, 238 [ expression] 105, 167 [ liste ] 34, 180, 184 [[: classe :]] 185, 186, 187 \ 34, 48, 68, 70, 180, 181, 184, 188, 189 ^ 180, 182, 183, 184 ` commande ` 47 { commandes } 89 | 13, 79, 91, 183 || 81, 88, 89, 108, 237 ~ 37, 150 Numriques 2> 94 2>&1 96, 97, 100 2>> 94 A Ada 1 AIX 2 alias 127 apostrophe 68, 69 at 151, 159 Awk 1, 5, 213, 216 B backslash 68, 180, 181, 189 basename 151 Bash 3, 5, 54, 55, 56, 57, 91, 137, 153, 177, 208 batch 151 bc 29, 44, 151, 177 BEGIN (Awk) 215, 216 bibliothque 239 break 113 buffer 98, 99 bzip2 151 C C/C++ 1, 2, 18, 51 calculs 44 case 18, 109, 117, 140, 142 cat 80, 140, 141, 151, 152, 167 cd 136 CDPATH 137 chmod 9, 103 cksum 151 clear 176 code de retour 78, 81, 104, 124, 135, 136, 158, 236 commande 13, 77 commentaire 10, 67 continue 113 cp 24 Csh 2, 3, 47 csplit 151 cut 79 Cygwin 5 D d (commande Sed) 198, 202 date 151 DEBUG 177 declare 56

260

Shells Linux et Unix par la pratique

dmon 162 df 79 dialog 176 diff 151 document en ligne 101 DOS 206 dos_2_unix 206 E echo 6, 22, 47, 68, 90, 91, 137, 151 ed 195, 196 Edlin 195 END (Awk) 215, 216 entre standard 90 environnement 51 EUID 41, 149 eval 73 exec 134, 168 exit 134, 135 export 51, 53, 54, 55 expr 151 F false 82, 151 fg 84 fo 167 le 135, 151 FILENAME (Awk) 224 nd 94, 151, 172, 192 fmt 151 fold 151 fonction 17, 50, 111, 117, 120 for 115, 157 fork 51, 133, 155 Fortran 1 FreeBSD 2 FS (Awk) 220 FSF 3 ftp 101 function 17, 120, 157 G getopts 18, 142, 145, 236 Gnu 3, 5, 137, 145, 152 grep 39, 41, 87, 95, 98, 151, 172, 179, 180, 190, 196 guillemet 48, 68, 69 gzip 151

H head 151 help 153 HOME 37, 136, 149 Hurd 2 I if 20, 87, 104, 113, 135, 156, 157, 167 IFS 138, 139, 141, 168 interprteur 1, 5, 9 K kill 151, 160, 164 Ksh 3, 5, 29, 54, 56, 57, 91, 139, 177 L L 43 LANG 78 LINENO 177 Linux 2 local 17, 50, 122, 157, 235 logger 162, 177 lpr 151 M man 9, 153 md5sum 151 mkfo 167, 170 mount 134 mv 24 mysql 104 N ncurses 175, 176 NetBSD 2 NF (Awk) 220, 222 nice 151 nohup 152, 160, 166 NR (Awk) 223 O od 152 OFS (Awk) 222 OPTARG 20, 144, 145 OPTERR 144 OPTIND 18, 142 ORS (Awk) 227, 231

P p (commande Sed) 198, 199, 201 paramtre 59 Pascal 18 paste 152 patch 151 PATH 7, 8, 17, 133 Pdksh 3 Perl 1, 51 PID 133, 134, 148, 158, 164 ping 88, 89, 147 pipeline 79, 91, 97 Posix 4 PPID 148 pr 152 print (Awk) 214, 216, 227, 231 printenv 56 printf (Awk) 152, 227, 231 prompt 6 ps 152, 162 PS3 116 PWD 37, 149 pwd 137, 150 Python 1 Q quote 68, 69 R read 22, 74, 87, 90, 91, 92, 138, 140, 168, 175 redirection 90 REPLY 117, 138, 140 return 118, 124 rm 14, 24, 25 root 24 RS (Awk) 217, 219, 220, 221 Ruby 1 S s (commande Sed) 198, 204 script 1, 4, 9, 131, 133, 135, 233 Sed 1, 79, 195, 197, 213 select 116 seq 152 set 31, 49, 61, 132, 176, 177, 235 setsid 162 Sh 2, 3 shebang 9, 10, 16, 216, 233

Index

261

shell 2, 13, 132 Bash 3, 5 Bourne 2 C 2, 47 Korn 3, 5, 29, 139 shift 20, 62, 64, 142 SIGCHLD 165 SIGCONT 84, 86 SIGHUP 160, 164 SIGINT 82, 84, 159, 164, 165 SIGKILL 164, 165 signal 82, 163 SIGQUIT 164 SIGSEGV 165 SIGSTOP 165 SIGTERM 164 SIGTSTP 165 SIGTTIN 84 SIGTTOU 84 SIGUSR1 164 SIGUSR2 164 sleep 82, 156, 157 Solaris 2, 233 sort 152 sortie derreur 90, 98, 100 sortie standard 90, 98, 100

source 16, 50, 133, 134, 150, 240 sous-chane 33 sous-shell 22, 55, 89 split 152 sprintf (Awk) 232 SQL 104 Stallman (Richard M.) 3 stty 80, 85, 152, 174, 175 sum 151 SUSv3 4, 5, 150 syslog 162, 177 T tail 79, 152 tar 24, 152 Tcl/Tk 1 Tcsh 3 tee 152, 171 terminfo 175 test 6, 21, 105 times 177 tostop 85 touch 142, 152 tput 175 tr 152 trap 23, 165

true 82, 113, 152 typeset 56 U UID 149 ulimit 177 umask 103 uname 109 Unix 2, 3, 155, 206 until 112 V variable 17, 29, 50, 235 environnement 51 locale 50 W wait 86, 157, 158 wc 152 while 18, 112, 113, 118, 140, 142, 175 Windows 5 X xargs 172, 174, 192

Shells
Linux et Unix par la pratique
C. Blaess Programmer des scripts puissants et portables
Les systmes Linux et plus gnralement Unix permettent aux utilisateurs, administrateurs, et dveloppeurs de raliser des tches compltes en regroupant simplement quelques instructions dans des fichiers de scripts. Mais pour cela, il faut auparavant matriser la puissance du shell, ainsi que la complexit apparente de sa syntaxe. Cet ouvrage vous aidera comprendre progressivement toutes les subtilits de ce langage de programmation, afin que vous soyez capable dcrire rapidement des scripts robustes, puissants et portables pour Bash ou shell Korn. Il comporte en outre une prsentation dtaille des outils Grep et Find, ainsi que des langages Sed et Awk dans leurs utilisations les plus courantes. Avec lappui de nombreux exemples et exercices corrigs, lauteur insiste sur la mise en pratique des notions abordes : des scripts complets prts lusage sont disponibles sur lextension web du livre, pour illustrer les mthodes de programmation proposes. Diplm de lEsigelec (Rouen) et titulaire dun DEA dintelligence artificielle, Christophe Blaess est ingnieur indpendant en informatique depuis une quinzaine dannes. Il ralise des missions de conseil et de dveloppement axes sur les aspects industriels de Linux (systmes embarqus, temps-rel, etc.), ainsi que sur ladministration et le dploiement de logiciels libres. Auteur de plusieurs ouvrages et de nombreux articles sur Linux, il anime des sances de formation professionnelle dans diffrents domaines touchant la programmation systme sous Unix et Linux.

qui sadresse cet ouvrage ?


Aux tudiants en informatique (1er et 2e cycles universitaires, coles dingnieurs) Aux programmeurs Linux et Unix Aux administrateurs systme en charge dun parc Linux ou Unix

Au sommaire
Principe des scripts shell Le shell Unix Excution dun script Programmation shell Premier aperu Premier script, rm_secure Analyse dtaille Performances Exemple dexcution valuation dexpressions Variables Calcul arithmtique Invocation de commande Portes et attributs des variables Paramtres Protection des expressions Tableaux valuation explicite dune expression lments de programmation shell Commandes et code de retour Redirections dentres-sorties Structures de contrle Commandes, variables et utilitaires systme Commandes internes Commandes externes Programmation shell avance Processus fils, paralllisme Arrire-plan et dmons Signaux Communication entre processus Entres-sorties Interface utilisateur Dboguer un script Virgule flottante Expressions rgulires - Grep Outil Grep Recherche rcursive avec find Sed Prsentation Utilisation de Sed Awk Fonctionnement de Awk Enregistrements et champs Structures de contrle Expressions Retour sur les affichages Bonne criture dun script Prsentation gnrale Variables Gestion des erreurs Fonctions Solutions des exercices.

Conception : Nord Compo

Sur le site www.editions-eyrolles.com Tlchargez le code source de tous les scripts de l'ouvrage Dialoguez avec l'auteur

Vous aimerez peut-être aussi