Linux & Bash · Chapitre 5

5. Script Shell

Jusqu’ici, nous avons utilisé Bash de manière interactive : une commande est saisie dans le terminal, Bash l’exécute, puis nous passons à la suivante. Un script shell permet de placer ces commandes dans un fichier afin de les exécuter automatiquement, puis d’ajouter de la logique avec des variables, des conditions, des boucles et des fonctions.

Fil conducteur : écrire des commandes dans un fichier → rendre ce fichier exécutable → mémoriser des valeurs → prendre des décisions → répéter des actions → regrouper du code dans des fonctions.

5.1 Du terminal au script Bash

5.1.1 Qu’est-ce qu’un script Bash ?

Bash est le shell que nous utilisons pour lancer des commandes. Il fournit aussi un langage permettant d’écrire ces commandes dans un fichier et de les organiser.

Un script Bash est donc simplement un fichier texte contenant des instructions Bash. L’intérêt est de pouvoir rejouer automatiquement une suite d’actions au lieu de les saisir manuellement à chaque fois.

5.1.2 Le shebang : quel interpréteur doit lire le fichier ?

Lorsque nous voulons exécuter directement un script, Linux doit savoir quel programme doit interpréter son contenu. On place pour cela un shebang sur la première ligne :

#!/bin/bash

Cette ligne indique que le script doit être interprété par Bash. Le principe n’est pas propre à Bash : un script Python peut par exemple commencer par #!/usr/bin/env python3.

5.1.3 Créer un premier script

Créons un fichier script.sh avec Nano :

nano script.sh

Ajoutons le shebang puis une commande qui liste le contenu de la racine / :

#!/bin/bash
ls /

Nous avons maintenant une suite de commandes enregistrée dans un fichier.

5.1.4 Deux façons d’exécuter le script

Première possibilité : demander explicitement à Bash de lire le fichier :

bash script.sh

Ici, nous lançons bash et lui donnons script.sh comme fichier à interpréter.

Deuxième possibilité : exécuter directement le fichier :

./script.sh

Cette fois, le système tente d’exécuter script.sh lui-même. Le shebang lui indique alors d’utiliser Bash. Mais le fichier doit posséder le droit d’exécution x, notion vue au chapitre précédent.

chmod +x script.sh
./script.sh
Lien avec le chapitre 4 : un script peut contenir du code parfaitement valide et pourtant refuser ./script.sh si le droit d’exécution n’est pas accordé.

5.2 Mémoriser et réutiliser des valeurs

5.2.1 Commenter le code

Une ligne commençant par # est un commentaire : elle sert à documenter le script et n’est pas exécutée.

# Cette ligne explique ce que fait le script

5.2.2 Créer une variable

Une variable associe un nom à une valeur. L’affectation se fait avec = :

my_variable=hello

Il ne faut pas mettre d’espace autour du signe =.

Si nous écrivons :

echo my_variable

Bash affiche le texte my_variable. Pour demander le contenu de la variable, on préfixe son nom avec $ :

echo $my_variable
Idée clé : my_variable est le nom ; $my_variable demande à Bash de remplacer ce nom par sa valeur.

Les variables Bash ne sont pas déclarées avec un type. Leur contenu est souvent manipulé comme du texte, mais certaines valeurs peuvent aussi être utilisées dans des calculs et des comparaisons.

5.2.3 Les guillemets : protéger ou interpréter du texte

Une affectation telle que :

my_variable=hello world

pose problème car l’espace sépare les éléments de la ligne. Pour conserver toute la phrase comme une seule valeur :

my_variable="hello world"
echo $my_variable

Les guillemets simples et doubles n’ont pas le même comportement :

echo 'the content of my variable is $my_variable'
echo "the content of my variable is $my_variable"
ÉcritureComportement
'...'Le contenu est conservé littéralement : $my_variable reste du texte.
"..."Bash remplace $my_variable par sa valeur.

5.2.4 Récupérer le résultat d’une commande

Un script a souvent besoin de stocker non pas une valeur écrite à l’avance, mais le résultat d’une commande. Le cours présente l’ancienne syntaxe avec apostrophes inversées :

my_variable=`ls -l`
echo $my_variable

La syntaxe moderne recommandée est :

my_variable=$(ls -l)
echo $my_variable

$(...) demande à Bash d’exécuter la commande située à l’intérieur puis de substituer son résultat.

5.2.5 Faire des calculs

Le cours utilise let pour effectuer des opérations arithmétiques :

let "a=1"
let b=2
let "c=b"
let "d = a + b * c"
echo $d

On retrouve notamment les opérateurs +, -, *, / et **.

5.3 Stocker plusieurs valeurs : les tableaux

5.3.1 Créer et lire un tableau

Une variable contient une valeur. Un tableau (array) permet d’en stocker plusieurs :

my_array=(hello world)

Chaque valeur possède un indice. Comme dans de nombreux langages, le premier indice est 0 :

echo ${my_array[0]}

5.3.2 Modifier et ajouter des éléments

my_array[0]=Hi
echo ${my_array[0]}

Pour afficher toutes les valeurs :

echo ${my_array[*]}

Les indices n’ont pas besoin de se suivre :

my_array[2]=or
my_array[4]=hello
my_array[1000]=world

echo ${my_array[*]}

5.3.3 Connaître les indices et le nombre d’éléments

echo ${!my_array[*]}   # indices présents
echo ${#my_array[*]}   # nombre d'éléments

5.4 Faire prendre des décisions au script

5.4.1 La structure if / then / fi

Un script devient réellement utile lorsqu’il peut adapter son comportement. La structure if exécute un bloc seulement si une condition est vraie :

prenom="Daniel"

if [ "$prenom" = "Daniel" ]
then
    echo "Salut Daniel !"
fi

On peut lire cette structure ainsi : si la condition est vraie, alors exécuter le bloc, puis terminer le if.

5.4.2 Ajouter else et elif

else permet de définir ce qu’il faut faire lorsque la condition est fausse :

prenom="Daniel"

if [ "$prenom" = "Daniel" ]; then
    echo "Salut Daniel !"
else
    echo "Bonjour $prenom !"
fi

elif permet de tester un autre cas avant d’arriver au else :

prenom="Diane"

if [ "$prenom" = "Daniel" ]
then
    echo "Salut Daniel !"
elif [ "$prenom" = "Diane" ]
then
    echo "Salut Diane"
else
    echo "Bonjour $prenom !"
fi

5.4.3 Comparer du texte et des nombres

TestSignification
$var1 = $var2égalité de chaînes de caractères
$var1 != $var2inégalité de chaînes de caractères
-z $variablechaîne vide
-n $variablechaîne non vide
$var1 -eq $var2égalité numérique
$var1 -ne $var2inégalité numérique
$var1 -gt $var2var1 > var2
$var1 -lt $var2var1 < var2
$var1 -ge $var2var1 >= var2
$var1 -le $var2var1 <= var2

5.4.4 Combiner plusieurs conditions

&& signifie ET : les deux conditions doivent être vraies. || signifie OU : au moins une condition doit être vraie.

prenom="Diane"
nom="Datascientest"

if [[ $prenom == "Daniel" && $nom == "Datascientest" ]]
then
    echo "Bonjour Daniel Data"
else
    echo "Bonjour $prenom $nom"
fi

5.4.5 [ ], [[ ]] et (( )) : quelle forme utiliser ?

En Bash, une condition peut être écrite de plusieurs façons. Elles se ressemblent, mais elles n'ont pas exactement le même rôle. [ ... ] est la forme historique et portable issue de la commande test. [[ ... ]] est une construction plus moderne du shell Bash (et de quelques shells proches), plus sûre et plus expressive. Pour les calculs et comparaisons purement numériques, Bash possède aussi (( ... )).

FormeÀ privilégier pourPourquoi
[ ... ]Scripts devant rester compatibles POSIX, tests simples de fichiers, chaînes ou nombresTrès largement portable entre les shells, mais demande davantage de précautions avec les variables et les opérateurs.
[[ ... ]]Scripts Bash : chaînes, fichiers et conditions composéesSyntaxe Bash plus robuste : pas de découpage en mots ni d'expansion de jokers sur les variables ; && et || s'intègrent naturellement.
(( ... ))Entiers et expressions arithmétiques en BashPermet d'écrire les comparaisons numériques avec les opérateurs habituels <, >, ==, <=, etc.
Repère simple : dans un script explicitement Bash (#!/bin/bash), utilisez généralement [[ ... ]] pour tester du texte ou des fichiers, et (( ... )) pour de l'arithmétique. Utilisez [ ... ] lorsque la compatibilité avec des shells POSIX est recherchée.

Chaînes de caractères

[ "$nom" = "Axel" ]
[[ $nom == "Axel" ]]

Les deux formes savent comparer des chaînes. Avec [ ], entourer les variables de guillemets est une précaution importante. Avec [[ ]], Bash protège mieux les expansions de variables et permet aussi des comparaisons plus riches, notamment avec des motifs.

Fichiers et répertoires

TestSignification
-e cheminle chemin existe
-f cheminc'est un fichier ordinaire
-d cheminc'est un répertoire
-r cheminil est lisible
-w cheminil est inscriptible
-x cheminil est exécutable

Ces opérateurs fonctionnent avec [ ] comme avec [[ ]]. Dans un script Bash, [[ -f $fichier ]] est généralement la forme la plus confortable ; dans un script POSIX, on utilisera plutôt [ -f "$fichier" ].

Nombres entiers

[ "$age" -ge 18 ]
[[ $age -ge 18 ]]
(( age >= 18 ))

[ ] et [[ ]] utilisent les opérateurs numériques historiques -eq, -ne, -lt, -le, -gt et -ge. En Bash, (( )) est souvent plus lisible pour les nombres car il utilise directement les opérateurs arithmétiques habituels.

Attention : dans [ ... ], < et > ne doivent pas être utilisés comme s'ils étaient des comparateurs numériques. Pour des entiers, utilisez -lt/-gt ou, en Bash, la forme arithmétique (( ... )).

5.5 Répéter des actions avec des boucles

5.5.1 while : répéter tant qu’une condition est vraie

let i=0

while [ $i -lt 10 ]
do
    let "i=i+1"
done

echo $i

La boucle teste i < 10. Tant que cette condition est vraie, elle augmente i de 1 puis recommence. Lorsque la condition devient fausse, le script sort de la boucle.

5.5.2 for : parcourir une série de valeurs

for x in '1st iteration' '2nd iteration' '3rd iteration'
do
    echo $x
done

À chaque tour, x reçoit la valeur suivante de la liste.

5.5.3 seq : générer une suite d’entiers

La commande seq génère une suite d’entiers entre une valeur de départ et une valeur de fin :

seq 3 22

Elle peut donc fournir facilement une série de nombres à traiter dans une boucle.

5.6 Regrouper du code avec des fonctions

5.6.1 Définir une fonction

Une fonction permet de donner un nom à un bloc d’instructions afin de pouvoir l’appeler lorsque nous en avons besoin. Le cours présente deux syntaxes :

my_function () {
    echo "Nous pouvons faire quelque chose ici"
}

ou :

function my_function {
    echo "Nous pouvons faire quelque chose ici"
}

5.6.2 Passer des arguments à une fonction

Les arguments reçus par une fonction sont accessibles par leur position : $1 pour le premier, $2 pour le deuxième, etc.

function my_function {
    echo "Premier argument"
    echo $1
    echo "Second argument"
    echo $2
}

Pour appeler la fonction :

my_function "Daniel" "24"

Dans cet appel, Daniel devient $1 et 24 devient $2.

5.6.3 Produire une valeur et la réutiliser

En Bash, une fonction ne reçoit pas des paramètres nommés entre parenthèses comme en JavaScript. Elle récupère ses arguments avec $1, $2, $3, etc.

Lorsqu’une fonction doit produire une donnée, elle l’écrit généralement sur sa sortie standard avec echo. Une autre fonction peut ensuite récupérer cette sortie avec $(...).

# Première forme : le mot-clé "function" est autorisé,
# mais il n'est pas obligatoire en Bash.
function add {
    local x=$1
    local y=$2

    # Cette fonction ne produit que le résultat de l'addition.
    echo $((x + y))
}

# Deuxième forme, sans le mot-clé "function".
added_to() {
    local x=$1
    local y=$2
    local who=$3

    echo "$who retourne $(add "$x" "$y")"
}

message=$(added_to 4 5 "Axel")
echo "$message"

Résultat :

Axel retourne 9

Le déroulement est le suivant :

  1. added_to 4 5 "Axel" appelle la fonction extérieure.
  2. $(add "$x" "$y") appelle add et récupère uniquement 9.
  3. added_to construit la chaîne Axel retourne 9.
  4. $(added_to ...) place cette chaîne dans la variable message.
Les deux écritures sont équivalentes : function add { ... } utilise le mot-clé facultatif function, tandis que add() { ... } utilise les parenthèses vides. Dans les deux cas, les arguments sont accessibles avec $1, $2, etc.
echo et return ne jouent pas le même rôle : echo produit une donnée récupérable avec $(...). return renvoie seulement un code de fin compris entre 0 et 255 : 0 signifie généralement succès et une autre valeur signale une erreur.

5.6.4 Le point-virgule

Le point-virgule ; sépare plusieurs commandes placées sur une même ligne. Il n’est généralement pas nécessaire lorsque chaque commande possède sa propre ligne.

echo "Bonjour"; echo "Au revoir"

add() { local x=$1; local y=$2; echo $((x + y)); }

Dans la fonction écrite sur une seule ligne, chaque ; termine une commande. Le dernier est nécessaire avant l’accolade fermante }.

5.7 Le chemin logique d’un script Bash

BesoinOutil BashRôle
Indiquer comment exécuter le fichier#!/bin/bashChoisir Bash comme interpréteur.
Exécuter directement le fichierchmod +x puis ./script.shDonner puis utiliser le droit d’exécution.
Mémoriser une valeurvariableRéutiliser une donnée plus tard.
Récupérer une commande$(commande)Stocker ou réutiliser sa sortie.
Stocker plusieurs valeurstableauRegrouper plusieurs éléments.
Choisir une actionif / elif / elseAdapter le comportement du script.
Répéterwhile, forAutomatiser une action répétitive.
Réutiliser un blocfonctionStructurer et éviter de répéter le code.
Récupérer la donnée d’une fonction$(fonction arguments)Capturer ce que la fonction écrit avec echo.
Signaler succès ou erreurreturn 0 ou autre codeRenvoyer le code de fin de la fonction.
Vision DevOps : ces mécanismes permettent de transformer une succession de commandes Linux en procédure automatisée : le script peut récupérer des informations, tester une situation, décider quoi faire, parcourir plusieurs éléments et réutiliser des traitements.
Bien. Ces aspects théoriques étant acquis. On peut maintenant utiliser Bash pour automatiser des tâches système de plus en plus concrètes.