Séance 5 : Automatisation et scripts

Variables et environnement

Variable

Le shell permet de définir et réutiliser des variables :

$ message="hello world"
$ echo $message
hello world

Avertissement

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'

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 <variable>

La commande unset suivi d'un nom de variable (sans le méta-caractère $) supprime la variable :

$ unset message
$ echo $message

$

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.

Exemple de configuration par l'environnement :

$ 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

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

Affiche sur la sortie standard toutes les variables d'environnement actuellement définie.

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.

Indication

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.

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 (d'après les noms des deux premiers caractères) ou shebang.

  • 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) 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 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 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 </etc/passwd

read [options] <varname>...

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.

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.

-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 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 </etc/passwd
then
    echo "je l'ai trouvé"
fi

Structure while

while condition
do
    commande 1
    commande 2
    # ...
done

Note

  • Comme pour le if, le condition est une ligne de commande.

Exemple :

while read line
do
    echo "Jacques a dit $line"
done
echo "Jacques a fini"

Lecture depuis un fichier avec while

La commande cat ré-écrit sur sa sortie standard le contenu de tous les fichiers qui lui sont passés en argument (son nom est une abbréviation de concatenate).

Boucle sur toutes les lignes d'un fichier :

cat my_file.txt | while read line
do
    # do something with $line
done

Structure for

for varname in liste de valeurs
do
    commande 1
    commande 2
done

Note

  • La variable prend successivement chaque valeur de la liste.

  • Pour chaque valeur, la liste de commande est exécutée.

Exemple :

for i in A B C "D E F"
do
    echo $i
done

Comme pour le reste, les variables sont substituées avant l'analyse de la ligne, donc une variable contenant plusieurs mots (séparés par des espaces) peut être utilisée après le mot-clé in.

for i in $valeurs
do
    echo $i
done

On peut également utiliser la substitution par guillemets inversés.

for grp in `id -Gn`
do
    echo "Vous appartenez au groupe $grp"
done

Avertissement

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.

[ <testarg> ]

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.

STRING1 = STRING2

Vrai si les deux chaînes sont égales.

STRING != STRING2

Vrai si les deux chaînes sont différentes.

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

-f FILENAME

Vrai si FILENAME est le nom d'un fichier classique existant.

-d FILENAME

Vrai si FILENAME est le nom d'un répertoire existant.

EXPR1 -a EXPR2

Vrai si les deux expressions sont vraies.

EXPR2 -o EXPR2

Vrai si l'une au moins des deux expressions est vraie.

! EXPR

Vrai si EXPR est fausse.

Pour une documentation exhaustive, tapez man [.

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

  • La valeur de retour d'un script est celle de la dernière commande exécutée par ce script.

  • La commande exit <integer> 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

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

Indication

  • la commande sleep <number> 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 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.

Avertissement

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.

Avertissement

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.