Introduction aux scripts shell
Objectifs du document
Ce document a pour objectif de vous faire découvrir les grands principes utilisés dans les scripts shell.
Après quelques généralités, seront abordés les mécanismes de base sur lesquels les scripts shell reposent :
-
composition de commandes (→ enchaînements, redirections)
-
substitution de variables, d’expressions et de commandes
-
protection de commandes
Seront enfin présentées les structures de contrôle permettant l’exécution conditionnelle ou en boucle de portions de scripts.
Qu’est-ce qu’un shell ?
-
De manière générale, c’est un programme qui constitue une interface vers le système d’exploitation
Le système d’exploitation est lui-même une interface vers le matériel.
-
2 formes :
-
graphique → GUI : Graphical User Interface
-
en ligne de commande → CLI : Command Line Interface
-
-
Sur un système, plusieurs shells peuvent être disponibles simultanément. Exemples :
-
CLI : bash (Bourne Again Shell ⇒ le plus répandu sur Linux), sh (Bourne shell ⇒ le shell “ancestral”), ksh (Korn shell), tcsh …
-
GUI : KDE, Gnome …
-
Qu’est-ce qu’un script shell ?

-
Un fichier texte éditable avec n’importe quel éditeur de texte (nano, vi, Kate, KWrite…)
-
Il contient essentiellement :
-
des commandes Linux traditionnelles (
ls
,mkdir
,chmod
…) qui seront exécutées les unes après les autres et automatiquement par le shell -
des structures de contrôle pour faire des tests (→ exécution conditionnelle de commandes) , des boucles (→ exécution répétitive de commandes)
-
des variables pour mémoriser des valeurs
-
-
Il est directement interprétable par le shell pour lequel il a été conçu (→ pas de compilation).
À quoi ça sert ?
-
À automatiser certaines tâches :
-
Lancer régulièrement des commandes disposant de nombreuses options sans avoir à les saisir à chaque fois et/ou se plonger dans la documentation pour déterminer lesquelles il faut employer
-
Assurer le suivi et la maintenance de machines ou services dans le cadre de la supervision d’un parc informatique (→ surveillance des espaces disque, analyse de fichiers de journalisation…)
-
Coder des outils : traitement automatique de fichiers texte, calcul
-
Lancer l’exécution de logiciels qui nécessitent un paramétrage complexe.
Le système Linux utilise lui-même abondamment les scripts shell dans sa phase d’initialisation (→ init System V, systemd).
-
…
-
-
En résumé, les scripts shell servent à être plus productif et notamment dans l’administration de systèmes informatiques.
Un 1ier script
-
fichier
hello.sh
(l’extension.sh
est arbitraire mais couramment utilisée) :# Afficher un message d accueil (1) echo "Bonjour monde !" (2)
1 un commentaire qui débute par `#' 2 la commande qui permet d’afficher un message à l’écran -
Exécution du script par le shell
sh
(Bourne shell):$ sh hello.sh Bonjour monde !
Le “Shebang”
Le “Shebang” est une séquence de 2 caractères (→ “#!”) à la suite desquels on indique le programme qui doit interpréter le script.
Ceci va permettre de lancer un script shell comme n’importe quel autre programme en saisissant directement son nom (précédé de son chemin) plutôt que de le passer en argument à l’interpréteur devant l’exécuter.
Rappel
Sous Linux, il existe plusieurs interpréteurs de commande (→ |
Illustration :
-
Intégration du “shebang” dans le fichier
hello.sh
:#! /bin/bash (1) # Afficher un message d accueil echo "Bonjour monde !"
1 on indique le shell pour lequel le script est conçu à l’aide de la séquence spéciale #!
nommée shebangLe shebang doit impérativement constituer les 2 premiers caractères du fichier.
-
Exécution du script :
$ chmod +x hello.sh (1) $ ./hello.sh (2) Bonjour monde !
1 on rend le script exécutable 2 on exécute le script simplement en tapant son nom dans le shell ⇒ celui-ci sera automatiquement interprété par le shell indiqué par le shebang (pas forcément le même que celui depuis lequel le script a été lancé)
Rappel : Interaction avec un programme

L’exécution de tout programme Linux suit toujours les mêmes règles :
-
il débute son exécution après l’avoir invoqué depuis le shell en tapant son nom suivi ou non d’options et/ou arguments (→ correspond au contenu du
char * argv[]
dans un programme en langage C) -
il lit les saisies de l’utilisateur depuis le flux d’entrée standard (→ par défaut : le clavier)
-
il affiche des messages sur le flux de sortie standard (→ par défaut : l’écran) pour informer l’utilisateur
-
il alerte l’utilisateur en envoyant les messages d’erreur sur le flux d’erreur standard (→ par défaut : l’écran)
-
il retourne enfin au système d’exploitation un compte-rendu d’exécution (exit code) sous forme d’entier dès qu’il se termine
$ ./hello.sh
Bonjour monde !
$ echo $?
0
-
argv : {"./hello.sh"}
-
stdin : ""
-
stdout : "Bonjour monde !\n"
-
stderr : ""
-
exit code : 0
(← on l’affiche avececho $?
)Un 0 indique que tout s’est bien passé.
Tout autre valeur indique une erreur.
$ mkdir /sbin/bidon
mkdir: /sbin/bidon: Permission denied
$ echo $?
1
-
argv : {"mkdir", "/sbin/bidon"}
-
stdin : ""
-
stdout : ""
-
stderr : "mkdir: /sbin/bidon: Permission denied"
-
exit code : 1
$ read -p "Votre choix ? "
Votre choix ? pizza
$ echo $?
0
-
argv : {"read", "-p", "Votre Choix ?"}
-
stdin : "pizza\n"
-
stdout : "Votre choix ? " puis "pizza\n"
-
stderr : ""
-
exit code : 0
Composition de commandes
-
Le principe d’exécution des programmes rappelé ci-dessus va permettre 2 fonctionnalités intéressantes :
-
l’enchainement conditionnel de commandes
-
exécution ou non d’une commande en fonction du code de retour de la précédente commande
-
-
la redirection
-
redirection des flux d’entrée, de sortie et d’erreur standard (stdin, stdout, stderr) vers/depuis des fichiers ,
-
redirection de la sortie standard d’une commande vers l’entrée standard d’une autre commande (→ tube ou pipe)
-
-
Enchaînement conditionnel de commandes
-
Séquence de commandes pouvant être interrompue selon la réussite ou l’échec de l’une d’entre elles
-
Mécanisme reposant sur la valeur des codes de sortie des programmes (0 = OK, <>0 = NOK)
$ cmd1 && cmd2 && cmd3 && ...
⇒ Exécute cmd2
si cmd1
réussit puis cmd3
si cmd2
réussit …
$ cmd1 || cmd2 || cmd3 || ...
⇒ Exécute cmd2
si cmd1
échoue puis cmd3
si cmd2
échoue …
$ cmd1 ; cmd2 ; cmd3 ; ...
⇒ Exécute cmd1
puis cmd2
puis cmd3
… quel que soient leurs résultats d’exécution (réussite ou échec)
Attention à la priorité des opérateurs (→ précédence)
Exemple :
|
Redirection des flux standards : ‘>’, ‘>>’, ‘<’
$ <cmd> > out.txt (1)
$ <cmd> >> out.txt (2)
$ <cmd> < in.txt (3)
1 | Exécute <cmd> et envoie sa sortie standard dans le fichier out.txt au lieu de l’écran.
|
2 | idem sauf que la sortie standard est ajoutée au contenu du fichier out.txt s’il existe (→ on ne perd pas son contenu d’origine). |
3 | l’entrée standard de cmd est lue depuis le fichier in.txt et non depuis le clavier |
echo
dans le fichier out.txt
$ echo "Bonjour" > out.txt (1)
$ cat out.txt
Bonjour
$ echo "Salut" > out.txt (2)
$ cat out.txt
Salut
$ echo " tout le monde !" >> out.txt (3)
$ cat out.txt
Salut tout le monde !
1 | on envoie “Bonjour” dans le fichier out.txt au lieu de l’écran |
2 | on envoie “Salut” dans out.txt . Puisque le fichier existe déjà, son contenu est écrasé |
3 | on envoie “tout le monde !” à la suite du contenu existant de out.txt |
$ ls /home/bonnie >catalog.txt ; ls /home/clyde >>catalog.txt
La commande Cela permet, par exemple, de garder dans un fichier une trace de la sortie d’une commande tout en permettant de la relayer à une autre commande (cf. Les tubes). Pour plus d’informations sur la commande |
Here documents : ‘<<’
-
L’opérateur
<<
possède une signification sensiblement différente : il est utilisé dans le cadre de ce qu’on appelle les here documents.Un here document permet de “simuler” un fichier en indiquant son contenu en ligne.
-
Exemple : Afficher le nombre de lignes du here document dont le contenu est délimité par la chaîne arbitraire
FIN
(FIN
doit impérativement être “collé” à<<
).$ wc -l <<FIN (1) > ligne n°1 > ligne n°2 > ligne n°3 > FIN 3 (2) $
1 appel de la commande wc -l
qui compte le nombre de ligne de ce qu’il lui est fournit en entrée, c’est-à-dire, ici, le here document2 le here document contient effectivement 3 lignes
Redirection du flux d’erreur : ‘2>&1’ ou '`&>`"
-
Il est possible de rediriger la sortie d’erreur et la sortie standard dans un même fichier.
-
Dans de nombreux scripts, on utilise la séquence
2>&1
à cet effet. -
Exemple : compter les lignes de 2 fichiers dont 1 n’existe pas
$ wc -l hello.sh fichier_inexistant > errout.txt 2>&1 (1) $ cat errout.txt (2) wc: fichier_inexistant: open: No such file or directory 3 hello.sh 3 total
1 La commande wc
ne provoque aucun affichage2 le fichier errout.txt
contient à la fois l’erreur (→stderr
) sur la 1ière ligne du fichier et le résultat de la commandewc
appliquée au fichierhello.sh
sur les 2 lignes suivantes (→stdout
).Interprétation de la commande :On redirige d’abord la sortie standard de la commande vers
errout.txt
puis on redirige la sortie d’erreur (représentée par le 2 dans2>&1
) vers l’endroit où est redirigée la sortie standard (représentée par le 1 dans2>&1
) ⇒ les sorties standard et d’erreurr sont redirigées dans le même fichier.
La page de manuel de bash suggère une autre syntaxe pour réaliser cette commande : $ wc -l hello.sh fichier_inexistant &> errout.txt (1)
Malgré une meilleure lisibilité, la 1ière syntaxe est encore largement répandue. |
-
Utilisation fréquente de la redirection
2>&1
: rendre “muet” un programme$ yes > /dev/null 2>&1
-
On redirige la sortie standard et d’erreur de la commande
yes
(→ envoie à l’infini le caractère ‘y’ sur stdout) sur le fichier/dev/null
qui offre la particularité d’ignorer et de perdre tout ce qu’on écrit dedans⇒ ceci permet de réduire à néant tout message émis sur le flux standard ou d’erreur :
-
aucun affichage
-
aucune utilisation de l’espace disque
-
-
Attention à l’emplacement de
2>&1 Une commande La preuve “en image” :
|
Les tubes
-
Les tubes sont unt type de redirection qui est également très fréquemment employé dans les scripts
-
Syntaxe
$ cmd1 | cmd2 (1)
1 On exécute cmd1
puiscmd2
en redirigeant la sortie standard decmd1
sur l’entrée standard decmd2
. -
Exemple : Compter le nombre de fichiers dans le répertoire
/var/log/apache2
$ ls /var/log/apache2/ | wc -l 3 # Sensiblement équivalent à : $ ls /var/log/apache2/ > fichier.txt $ wc -l < fichier.txt 3 # ...mais le résultat intermédiaire n'est pas stocké # (-> on gagne 36 octets sur le disque) $ ls -l fichier.txt -rw-r--r--@ 1 claude staff 36 7 avr 14:47 fichier.txt
L’utilisation des tubes n’est possible qu’avec les commandes de type “filtre” c.-à-d. les commandes qui acceptent de prendre leur entrée standard depuis la sortie d’un tube (ex. Il existe néanmoins une solution qui consiste à utiliser la commande Exemple :
Consulter internet pour des exemples plus complets. Ex. : Linux commands: xargs |
Substitution
-
Un shell Linux est capable d’évaluer un certain nombre d’expressions au cours de l’exécution d’un script. Lorsqu’il en rencontre une, il la remplace/substitue par le résultat de son évaluation puis reprend l’interprétation du script
-
Parmi les expressions candidates, on trouve :
-
les variables,
-
les caractères génériques ou jokers (→ wildcards)
-
les expressions arithmétiques et bit-à-bit
-
les commandes
-
Substitution de variables
-
Variable : nom auquel on associe une valeur
-
Définition d'1 variable : <nom_variable>=<valeur>
-
Utilisation d'1 variable : $<nom_variable> ou ${<nom_variable>}
-
Plusieurs types de variables :
-
variables utilisateurs
⇒ variables définies par l’utilisateur. ex. :
$mon_age
-
variables d’environnement
⇒ variables définies par le système. ex. :
$PATH
-
variables internes
⇒ variables définies par le shell. ex. :
$@
Variables utilisateurs
#! /bin/sh
# script : uservariable.sh
question= "Tu fais koi ?"
option=IR
formation="BTS CIEL"
echo $question
echo "J'suis en $formation_$option ...euh... ${formation}_$option)"
$ chmod +x uservariable.sh
$ ./uservariable.sh
Tu fais koi ?
J'suis en IR ...euh... BTS CIEL_IR
Noter la différence entre Dans la 1ière forme, le shell cherche une variable nommée formation_ (← le `_' est un caractère autorisé dans le nom d’une variable), ne la trouve pas et la remplace donc par une chaîne vide. |
Variables d’environnement
$ printenv (1)
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/bash
[...]
HOME=/Users/claude
LOGNAME=claude
$ echo "Mon nom d'utilisateur est $LOGNAME et mon shell est $SHELL."
Mon nom d'utilisateur est claude et mon shell est /bin/bash.
1 | : la commande printenv affiche les variables d’environnement |
Variables internes
-
Le shell définit un certain nombre de variables spéciales très utiles pour écrire les scripts :
-
$0
,$1
,$2
, … : les constituants de la ligne de commande qui a invoqué le script-
$0
: le nom du script -
$1
,$2
, … : les arguments du script
-
-
$@
: la liste des arguments ($1
+$2
+ …) -
$#
: le nombre d’arguments fournis au script -
$?
: le code de retour du dernier programme/script exécuté -
…
-
-
Exemple :
#! /bin/sh
# script : internalvariables.sh
echo "Hello from script $0 -> $# argument(s) provided"
echo "1st arg : $1"
echo "2nd arg : $2"
$ ./internalvariable.sh A
Hello from script ./internalvariable.sh -> 1 argument(s) provided
1st arg : A
2nd arg :
$ ./internalvariable.sh A B
Hello from script ./internalvariable.sh -> 2 argument(s) provided
1st arg : A
2nd arg : B
$ ./internalvariable.sh A B C
Hello from script ./internalvariable.sh -> 3 argument(s) provided
1st arg : A
2nd arg : B
$ ./internalvariable.sh "A B" C
Hello from script ./internalvariable.sh -> 2 argument(s) provided
1st arg : A B
2nd arg : C
Caractères génériques
-
Certains caractères prennent une signification particulière pour le shell (cf. `<', `>', ’|', …)
⇒ ce sont les Méta-caractères
-
Les méta-caractères suivants sont utilisés comme “joker” pour les noms de fichier ou de répertoire : ?, *, !, [ et ].
-
?
→ remplace n’importe quel caractère individuel -
* → remplace un ensemble de caractères consécutifs
-
[<plage_valeurs>]
→ remplace un caractère parmi ceux spécifiés dans <plage_valeurs>Si on remplace
<plage_valeurs>
par!<plage_valeurs>
ceci inverse la signification de
-
-
Exemples :
# Liste les dossiers débutant par 'D' $ ls -d D*/ Desktop/ Documents/ Downloads/ # Liste les dossiers débutant par 'D' et se terminant par 'p' $ ls -d D*p/ Desktop/ # Liste les dossiers dont la 2ième lettre est 'o' et la dernière est 's' $ ls -d ?o*s/ Documents/ Downloads/ # Liste les dossiers débutant par 'D' et dont la 3ième lettre est 's' ou 'w' $ ls -d D?[sw]*/ Desktop/ Downloads/ # Liste les dossiers débutant par 'D' et dont la 3ième lettre n'est ni 's' ni 'w' $ ls -d D?[!sw]*/ Documents/
Expressions arithmétiques & bit-à-bit
-
Le shell est capable d’évaluer des expressions arithmétiques simples et bit-à-bit avec la notation suivante :
$((<expression>))
-
Exemples :
# Division entière $ echo $((10/6)) 1 # Reste de la division entière (modulo) $ echo $((10%6)) 4 # Décalage d'un rang vers la droite $ echo $((10>>1)) 5 # OU bit à bit $ echo $((10|7)) 15
Substitution de commandes
-
Le shell sait capturer la sortie standard d’une commande pour l’utiliser par la suite.
-
Syntaxe : $( <commande> )
On peut rencontrer une autre syntaxe qui n’est pas recommandée : ` <commande> `
-
Exemple :
# On mémorise dans 'annee' la sortie standard de la commande : # date "+%Y" puis on affiche cette variable pour avoir l'année courante $ annee=$(date "+%Y") $ echo $annee 2023 # On utilise ensuite la variable $ echo "L'an prochain, nous serons en $((annee+1))." L'an prochain, nous serons en 2024.
Protections des expressions
-
On a vu que certains caractères étaient interprétés par le shell.
-
Pour éviter cette interprétation, 3 moyens existent :
-
l’antislash ou échappement (\) → annule le sens particulier du méta-caractère qui le suit.
-
Exception : l’antislash suivi d’un retour à la ligne
⇒ utilisé, par ex., pour continuer la saisie d’une commande à la ligne suivante
-
-
les apostophes ('...') → tous les caractères compris entre les apostrophes perdent leur signification spéciale.
-
les doubles guillemets ("…") → annule le sens particulier des caractères qu’ils renferment à l’exception de
$
,`
(backquote) et, dans certaines conditions, de|
puis!
-
Exécution conditionnelle : if/then/else/fi
-
Comme tout langage de programmation, le shell propose une structure qui permet d’exécuter du code ou non selon qu’une condition est remplie ou non
-
Syntaxe :
if cmd1 then cmd2 else cmd3 fi # OU de manière plus condensée if cmd1 ; then cmd2 ; else cmd3 ; fi
-
cmd2
exécutée sicmd1
exécutée avec succès -
cmd3
exécutée sicmd1
a échoué
-
-
cmd1
est la plupart du temps la commandetest
qui permet d’évaluer une conditionExemple :#!/bin/bash state="on" if test "$state" = "on" then echo "Allumé !" else echo "Eteint !" fi
Toujours entourer les références aux variables par des double côtes lorsqu’elles sont employées dans des conditions de façon à traiter correctement des variables vides. Exemple :
L’exécution de ce script provoque une erreur :
|
Exécution conditionnelle : la commande test
-
Exemples de syntaxe :
-
test -f
<fichier>→ VRAI (code de retour 0) si le fichier existe
-
test -z
<chaîne>→ VRAI si la chaîne de caractères est vide
-
test
<chaîne1>=
<chaîne2>→VRAI les 2 chaînes sont identiques
-
test
<nombre1>-lt
<nombre2>→ VRAI si <valeur1> est plus petite (less than) que <valeur2>
-
|
Les espaces sont obligatoires autour de la condition dans la syntaxe |
-
Exemple n°1 : test de valeur de variables de type chaine ou numérique
$ cat dummy-test.h #!/bin/bash state="on" nombre=1789 if [ "$state" = "on" ] then echo "Allumé !" else echo "Eteint !" fi if [ $(("$nombre" % 2)) -eq 0 ] then echo "Nombre pair" else echo "Nombre impair" fi $ ./dummy-test.sh Allumé ! Nombre impair
-
Exemple n°2 : Déterminer le siècle de l’année courante
$ cat getcentury.sh #! /bin/sh annee=$(date "+%Y") if test "$annee" -lt 2001 # ou [ "$annee" -lt 2001 ] then echo "$annee -> 20ième siècle" else echo "$annee -> 21ième siècle" fi $ ./getcentury 2023 -> 21ième siècle
Exécution conditionnelle : Combinaison de plusieurs conditions
-
il est possible de combiner plusieurs conditions :
Ex. : Combinaison de 2 conditions avec un OU logique
if [ cond1 ] || [ cond2 ] ; then cmd1 ; fi (1)
1 cmd1
sera exécutée si l’une ou l’autre des conditionscond1
etcond2
est vérifiée voire les 2. -
Il existe 3 opérateurs logiques pour combiner des conditions :
-
le OU logique ⇒ codé
||
-
le ET logique ⇒ codé
&&
-
le NON logique ⇒ codé
!
-
-
Exemple :
#! /bin/sh temperature=16 etatChauffage="OK" if [ $temperature -lt 18 ] && [ ! $etatChauffage == "HS" ] then echo "Déclenchement du chauffage" (1) fi
1 Le chauffage n’est déclenché que si la température est inférieure à 18°C et que le chauffage n’est pas HS.
Exécution en boucle
-
Le shell propose plusieurs structures qui permettent d’exécuter des portions de script en boucle :
for…do…done
,while…do…done
-
Le
for…do…done
est utilisé lorsque le nombre d’itérations est connu à l’avance -
2 formes :
-
for <variable> in <list> ; do <cmds> … ; done
⇒ <variable> prend successivement les valeurs présentes dans <list> et la séquence de commandes <cmds> … est exécutée suite à chaque affectation.
-
for (( <expr1> ; <expr2> ; <expr3> )) ; do <cmds> … ; done
⇒ <expr1> est évaluée 1 fois puis <expr2> est évaluée de manière répétitive jusqu’à ce qu’elle prenne la valeur 0. A chaque fois que <expr2> est évaluée comme différente de 0, la séquence de commandes <cmds> … est exécutée et <expr3> est évaluée.
-
-
Exemple 1 :
$ cat comptine1.sh #! /bin/bash for i in {1..3} ; do echo -n "$i, "; done (1) echo "... nous irons au bois" $ ./comptine1.sh 1, 2, 3, ... nous irons au bois
1 on affiche successivement les valeurs que prend la variable i
durant l’exécution de la boucle -
Exemple 2 :
$ cat comptine2.sh #! /bin/bash for ((i=4; i < 7; i++)) ; do echo -n "$i, "; done (1) echo "... cueillir des cerises" $ ./comptine2.sh 4, 5, 6, ... cueillir des cerises
1 on fait la même chose que dans l’exemple précédent mais, cette fois-ci, avec la 2ème syntaxe
Pour finir
Ce cours a permis d’aborder les mécanismes de base proposés par bash
pour réaliser des scripts shell.
La puissance des scripts shell ne réside cependant pas uniquement sur ces mécanismes mais aussi sur la multitude et la cohérence des commandes mises à disposition par Linux.
La découverte de ces commandes dépasse le cadre de ce document.
Il est donc recommandé de d’abord se renseigner sur les commandes “incontournables” de Linux avant d’implémenter des scripts shell “sérieux”.
Par exemple, des commandes comme tee
, xargs
, sed
, grep
, find
(notamment avec son option -exec
→ voir Using the find -exec Command Option ),
awk
… peuvent être d’une grande utilité.
🞄 🞄 🞄