====================================== Séance 5 : Automatisation et scripts ====================================== .. include:: commondefs.rst.inc .. ifslides:: .. include:: credits.rst.inc Variables et environnement ========================== .. index:: variable, variable;de shell Variable -------- Le shell permet de définir et réutiliser des variables :: $ message="hello world" $ echo $message hello world .. warning:: Il ne faut *pas* d'espace autour du signe ``=``. .. note:: Les guillemets sont nécessaires si la valeur de la variable contient des espaces Le méta-caractère $ ------------------- Le méta-caractère dollar (``$``), suivi d'un nom de variable, est remplacé par la valeur de la variable en question. Cette substitution a lieu *avant* d'analyser les autres caractères. Ainsi, si la valeur de la variable contient des espaces, cela pourra donner lieu à plusieurs arguments dans la ligne de commande :: $ cp $message # copie le fichier 'hello' en 'world' .. nextslide:: .. important:: Le méta-caractère dollar n'est **pas inhibié** par les guillemets doubles (``"``). En revanche, il l'est par les guillemets simples (``'``). :: $ echo "Simon says $message" Simon says hello world $ echo 'Simon says $message' Simon says $message .. note:: Si le nom utilisé après `$` ne correspond à aucune variable définie, il est substitué par une chaîne vide (sans causer d'erreur). unset ---------------- La commande ``unset`` suivi d'un nom de variable (*sans* le méta-caractère ``$``) supprime la variable :: $ unset message $ echo $message $ .. index:: environnement, environnement;variable, variable;d'environnement, export Variable d'environnement ------------------------ Pour définir une **variable d'environnement**, il faut précéder son affectation du mot-clé `export` :: export MANPAGER=more .. note:: Par convention, les variables d'environnement sont en majuscules, alors que les variables de shell sont en minuscules. Différences ----------- * Les variables de shell + sont gérées en interne par le shell ; + ne sont pas accessible aux autres processus. * Les variables d'environnement + sont gérées par le |SE| ; + sont héritées par les processus lancés depuis le shell ; + sont utilisées par certains programmes pour modifier leur comportement. .. nextslide:: Exemple de configuration par l'environnement : .. code-block:: :emphasize-lines: 6,10,16 $ man ls # suivi de CTRL+Z $ ps -H PID TTY TIME CMD 28608 pts/4 00:00:00 bash 29162 pts/4 00:00:00 man 29172 pts/4 00:00:00 pager 29188 pts/4 00:00:00 ps $ kill %1 $ export MANPAGER=more $ man ls # suivi de CTRL+Z $ ps -H PID TTY TIME CMD 28608 pts/4 00:00:00 bash 29425 pts/4 00:00:00 man 29435 pts/4 00:00:00 more 29442 pts/4 00:00:00 ps .. index:: PATH, PS1 Quelques variables d'environnement ---------------------------------- ``PATH`` liste des répertoires, séparés par ``:``, dans lesquels le shell cherche les programmes à lancer ``PS1`` prompt affiché par le shell .. note:: La variable d'environnement ``PATH`` ne contient pas le répertoire courant ``.``, et ce pour des raisons de sécurité. Cela évite de lancer par erreur un programme local nommé ``ls`` ou ``cp``, et qui pourrait avoir des effets inattendus (ou malicieux). env --- .. program:: env Affiche sur la sortie standard toutes les variables d'environnement actuellement définie. .. index:: script Scripts ======= Un ensemble de commandes shell peuvent être stockées dans un fichier, pour être réutilisées plus tard. Méthode 1 : la commande source ------------------------------ :: $ source commandes.sh (sortie des instructions contenues dans commandes.sh) $ . commandes.sh # '.' est équivalent à 'source' (sortie des instructions contenues dans commandes.sh) Execute **dans le shell en cours** les commandes contenues dans le fichier ``commandes.sh``. .. hint:: Ce n'est pas la méthode la plus fréquente, notamment parce que les variables utilisées dans le fichier pourraient « polluer » le shell courant. On l'utilise justement lorsque l'objectif est de modifier le shell courant ou les variables d'environnement. .. index:: hash-bang, shebang, interpréteur Méthodes 2 : script exécutable ------------------------------ * La première ligne du fichier doit être ``#!/usr/bin/bash``. * L'utilisateur doit avoir les droits en exécution sur le fichier. * Le fichier peut alors être lancé comme n'importe quel programme :: $ ./commandes.sh (sortie des instructions contenues dans commandes.sh) Le shell exécutant les commande du script est un processus séparé du shell courant. .. note:: * La première ligne est appellée `hash-bang`:eng: (d'après les noms des deux premiers caractères) ou `shebang`:eng:. * Cette ligne indique à Linux quel *interpréteur* utiliser pour exécuter le fichier ; on peut y préciser n'importe quel exécutable, par exemple ``/usr/bin/python``, ``/usr/bin/ruby``... Commentaires ------------ Le caractère dièse (``#``, plus exactement `hash`:eng:) introduit un commentaire, ignoré par le shell. Le commentaire s'étend jusqu'à la fin de la ligne. :: $ echo texte affiché # commentaire non affiché texte affiché .. note:: Puisque le `shebang`:eng: commence par `#`, il est ignoré par le shell lui même, seul le |SE| l'interprète. Arguments --------- Lorsqu'on appelle un script (quelle que soit la méthode), on peut lui passer des arguments en ligne de commande. Ces arguments sont accessibles dans des variables spéciales : * ``$1``, ``$2``, ``$3``... contiennent les différents arguments, * ``$@`` contient la séquence entière d'arguments. Flux standards -------------- Rappel : tout processus lancé par un shell (y compris `via`:lat: un script) hérite de ses flux (entrée, sortie, erreur) standards. Ainsi, les redirections appliquées à un script s'appliquent aux commandes lancées par ce script. Contenu du fichier ``sort_column.sh``:: #!/usr/bin/bash # extrait et trie une colonne # des données passées sur l'entrée standard cut -d: -f$1 | sort Exemple d'utilisation:: $ ./sort_column.sh 1 ... --------------------------- .. program:: read Lit une ligne de texte sur l'entrée standard, et affecte le texte lu dans une ou plusieurs variable(s). Par défaut, la lecture s'interromp au premier saut de ligne. Retourne un succès si du texte est lu, un échec si la fin de fichier est rencontrée. .. describe:: varname Le texte lu est découpée au niveau des espaces ; la première variable reçoit le premier mot, la deuxième variable, le deuxième mot... et la dernière variable reçoit le reste du texte lu. .. option:: -n [nchars] Interromp la lecture au bout de ``nchars`` caractères. .. note:: Avec un seul nom de variable, l'intégralité du texte lu est affecté à cette variable. Structures de contrôle ====================== Le shell fournit des structures de contrôles similaires à celles des langages de programmation. Structure if ------------ :: if condition then commande 1 commande 2 # ... else commande 3 # ... fi .. note:: * ``condition`` est une ligne de commande ; la `valeur de retour `:ref: du processus détermine si c'est le ``then`` (succès) ou le ``else`` (échec) qui sera exécuté. * La clause ``else`` est facultative. * Le mot-clé ``fi`` est formé comme l'inverse du mot-clé ``fi``. * Comme partout ailleurs, les sauts de ligne peuvent être remplacés par des points-virgules (``;``). * L'indentation n'est strictement requise, mais conseillée pour la lisibilité du code. Exemple :: if grep -q charlie `:ref:. :: for grp in `id -Gn` do echo "Vous appartenez au groupe $grp" done .. warning:: Attention avec ce dernier exemple ; il ne fonctionne pas comme attendu si certains éléments contiennent des espaces. Précaution avec la boucle for ````````````````````````````` N'écrivez **pas** :: for filename in `ls` do cp $filename $filename.bak done Mais écrivez :: ls | while read filename do cp "$filename" "$filename.bak" done .. note:: ``ls`` écrit un nom de fichier par ligne ; ainsi, chaque nom de fichier sera lu en une fois par ``read``, même s'il contient des espaces. Cette précaution vaut bien sûr pour d'autres données, dont certaines lignes peuvent contenir des espaces. [ ] ------------- .. program: [ La commande `[` évalue une expression booléenne, décrite par ses arguments, et retourne un code de statut correspondant. Le dernier argument doit être ``]``. NB: les espaces séparant les opérateurs et les opérandes sont indispensable, pour que le shell les transmette à la commande ``[`` comme des arguments séparés. .. describe:: STRING1 = STRING2 Vrai si les deux chaînes sont égales. .. describe:: STRING != STRING2 Vrai si les deux chaînes sont différentes. .. nextslide:: .. describe:: INTEGER1 -lt INTEGER2 Vrai si les deux opérandes sont des entiers, et que INTEGER1 est strictement inférieur à INTEGER2. Existent également ``-le`` (≤), ``-gt`` (>), ``-ge`` (≥). .. describe:: -f FILENAME Vrai si FILENAME est le nom d'un fichier classique existant. .. describe:: -d FILENAME Vrai si FILENAME est le nom d'un répertoire existant. .. nextslide:: .. describe:: EXPR1 -a EXPR2 Vrai si les deux expressions sont vraies. .. describe:: EXPR2 -o EXPR2 Vrai si l'une au moins des deux expressions est vraie. .. describe:: ! EXPR Vrai si EXPR est fausse. Pour une documentation exhaustive, tapez ``man [``. .. nextslide:: Exemple :: #!/usr/bin/bash if [ -d "$1" -a -f "$2" ] then cp "$2" "$1"/"$2".bak else echo "Invalid arguments" >&2 exit 1 fi .. note:: ``>&2`` est une redirection particulière, qui redirige la sortie standard vers l'erreur standard. Elle permet ici d'utiliser ``echo`` pour écrire sur l'erreur standard (alors que son comportement normal est décrire sur la sortie standard). Valeurs de retour ================= .. program: exit * La valeur de retour d'un script est celle de la dernière commande exécutée par ce script. * La commande ``exit `` sort immédiatement du script, en retournant la valeur passée en argument. * En l'absence d'argument, ``exit`` sort du script avec la valeur de retour de la dernière commande exécutée. Valeurs de retour intermédiaire ------------------------------- * L'échec d'une commande dans un script n'entraîne **pas** la fin du script. * Si on souhaite interrompre un script en cas d'erreur d'une de ses commandes intermédiaires, on peut la faire suivre de ``|| exit``. Épilogue ======== * Le shell permet d'exécuter des tâches relativement complexes, en combinant plusieurs commandes spécialisées. * Les scripts shells permettent de stocker et réutiliser des séquences de commandes. * Mais le shell n'est **pas adapté** pour développer des applications complètes. * Lorsqu'un script devient trop complexe, il ne faut pas hésiter à le **ré-écrire** dans un vrai langage de programmation (quitte à garder certaines parties sous forme de script shell). Travaux dirigés =============== .. ifslides:: `Consultez les ici <../s5.html#travaux-diriges>`_ .. ifnotslides:: Exercice 1 ---------- Écrivez un script qui attend jusqu'à quatre arguments sur la ligne de commande, et les affiche en ordre inverse. Les arguments au delà du quatrième seront ignorés. Exercice 2 ---------- Écrivez une variante du script précédent, qui attend *exactement* quatre arguments. Il affichera un message d'erreur, et retournera un code d'échec, si l'utilisateur saisit le mauvais nombre d'arguments. Exercice 3 ---------- Écrivez une variante du script précédent, ou les quatre mots seront attendus sur l'entrée standard (sur une seule ligne) plutôt que comme arguments. Exercice 4 ---------- Écrivez un script qui crée 5 fichiers ``fic1.txt`` à ``fic5.txt``, contenant respectivement les valeurs ``1`` à ``5``, dans un répertoire passé en paramètre. Si ce répertoire n'existe pas, ou si un des cinq fichiers existe déjà, un message d'erreur doit être affiché. Exercice 5 ---------- Écrivez un script qui affiche, sur la même ligne, un compte à rebours de 5 secondes, puis affiche sur la ligne suivante le mot « décollage ». .. hint:: * la commande ``sleep `` attend sans rien faire pendant le nombre de secondes passé en paramètres ; * l'option ``-n`` de la commande ``echo`` lui indique de ne pas afficher de retour à la ligne. Exercice 6 ---------- Écrivez un script qui affiche le nom de tous les fichiers du répertoire ``/usr/include`` dont le nom se termine par ``.h`` et ayant plus de 100 lignes. Exercice 7 ---------- Dans cet exercice et le suivant, on réutilisera le fichier `contacts.txt <_static/contacts.txt>`_ utilisé lors d'une précédente séance. Écrivez une commande ``contacts_ville.sh`` qui prend en paramètre le nom d'une ville, et affiche uniquement les lignes du fichier ``contacts.txt`` concernant un habitant de cette ville. .. warning:: La ligne de commande ``./contacts_ville.sh Lyon`` devrait retourner uniquement Alice, Bob et Jean Dupont. Jane Lyon (qui habite Paris) ne **doit pas** être affichée. **Variante** : faites en sorte que la recherche de la ville soit insensible à la casse (majuscule-minuscule). Exercice 8 ---------- Écrivez une commande ``info_contact.sh`` qui prend deux paramètres : * le nom ou une partie du nom d'un contact, * l'une des deux chaînes ``ville`` ou ``tel``, et qui affiche, pour tous les contacts dont le nom contient le premier argument, son nom suivi de sa ville ou de son numéro de téléphone (selon le deuxième argument). Si le deuxième argument n'est pas une des deux chaînes attendues, le script doit afficher un message d'erreur et retourner un code d'échec. .. warning:: La ligne de commande ``./info_contacts.sh lyon tel`` devrait afficher uniquement le numéro de téléphone de Jane Lyon, mais *pas* celui d'Alice, Bob ou Jean Dupont. **Variante** : faites en sorte que le chemin du fichier ``contacts.txt`` soit pris dans la variable d'environnement ``CONTACTS``. À défaut, utilisez ``./contacts.txt``.