Administration Unix - CM séance 05
6. Les scripts bash
Un script est une succession de commandes, placées dans un fichier texte, exécutées dans un processus fils.
Un script shell est donc exécuté dans un shell fils (= un sous-shell), ce qui signifie que les variables du script ne seront pas visible du shell appelant le script.
6.1. Premier script
Créons un script hello.sh
, en utilisant par exemple l'éditeur vi
:
#! /bin/bash
echo "Hello World!"
Après l'avoir enregistré, on rend le script exécutable avec chmod
:
$ chmod +x hello.sh
$ ls -l hello.sh
-rwxrwxr-x ... hello.sh*
On peut maintenant exécuter le script de cette façon (./
indique au système
qu'il est situé dans le répertoire courant) :
$ ./hello.sh
Hello World!
Le shebang
Sous Unix, tous les scripts commencent par les deux caractères #!
,
appelé le shebang (ou encore shabang, de sharp #
bang !
).
Le shebang est un mot magique, un mot placé au tout début du fichier, qui permet au système d'identifier le type du fichier par son contenu.
Il est utilisé par la famille de fonctions C exec
pour distinguer les scripts
des exécutables binaires.
La commande file
utilise aussi le mot magique (ainsi que d'autres propriétés :
encodage, droits, extension...), en s'appuyant sur une base de données de
critères (les fichiers /etc/magic
et /usr/share/misc/magic.mgc
).
$ file hello.sh
hello.sh: Bourne-Again shell script, ASCII text executable
L'interpréteur
Après le shebang il y a le chemin absolu de l'interpréteur ; exemples :
#! /bin/bash
#! /bin/sh
#! /bin/dash
#! /bin/tcsh
#! /bin/zsh
#! /usr/bin/env python3
#! /usr/bin/env php
#! /bin/false
...
Lorsqu'on exécute un script, le système va exécuter l'interpréteur dans un processus fils en lui passant le chemin du fichier en premier argument.
Ainsi lorsqu'on tape ./hello.sh
, le système va exécuter : /bin/bash ./hello.sh
⟶ bash interprète les commandes du fichier,
la première ligne étant prise comme un commentaire (#...
).
= mécanisme extensible, car on peut rajouter des interpréteurs dans un système ; introduit en 1980 par Dennis Ritchie.
Certains interpréteurs sont situés dans un répertoire connu (bash
, sh
, ...).
D'autres peuvent être situés dans un répertoire qui dépend du système (python3
,
php
, ...), c'est pourquoi on les lance avec la commande /usr/bin/env
, dont
le but est de trouver leur chemin (à partir de $PATH
).
Si on commence le fichier par /bin/false
, le script échoue immédiatement :
c'est un moyen d'interdire l'exécution d'un script.
Remarques :
- l'extension
.sh
du fichier n'est pas utilisée par le système, on peut l'omettre ; - on peut aussi accoler shebang et interpréteur :
#!/bin/bash
6.2. Débogage
Pour savoir ce que fait un script, on peut utiliser set -x
(voir le
cours n°4 sur le prompt PS4
) :
$ cat ex1.sh
#! /bin/bash
set -x # active le débogage ; set +x pour le désactiver
dest="resultat.txt"
echo "Création du fichier $dest..."
echo -n "# Fichier créé le " >| "$dest"
date >> "$dest"
$ ./ex1.sh
+ dest=resultat.txt
+ echo 'Création du fichier resultat.txt...'
Création du fichier resultat.txt...
+ echo -n 'Fichier créé le '
+ date
$ cat resultat.txt
# Fichier créé le mardi 20 octobre 2021, 12:48:51 (UTC+0200)
ou encore invoquer bash -x
au shebang :
$ cat ex1.sh
#! /bin/bash -x
#set -x
...
ou encore, invoquer bash -x
dans le terminal :
$ bash -x ex1.sh
6.3. Code de terminaison
Tout processus qui se termine émet un code de terminaison (appel C exit
)
qui sera reçu par le processus père (appel C wait
).
- Ce code peut être :
- 0 signifie que l'opération a réussi
- 1..255 signifie un échec
Certains programmes donnent des codes différents selon l'erreur rencontrée,
et les documentent dans la page du man (ex: man ls
, /exit status
).
Lorsqu'un shell ou un script exécute une commande en avant plan, à sa
terminaison il recevra le code de terminaison dans la variable spéciale ?
.
$ ls bidon
ls: impossible d'accéder à 'bidon': Aucun fichier ou dossier de ce type
$ echo $?
2 # ls a échoué
$ echo $?
0 # echo a réussi
chaque commande appelée modifie
$?
, y compris echo
.
Dans un script, le code de terminaison à la sortie du script sera celui de la dernière commande exécutée.
$ cat essai-ls.sh
#! /bin/bash
echo "listing de bidon :"
ls bidon
$ ./essai-ls.sh
listing de bidon :
ls: impossible d'accéder à 'bidon': Aucun fichier ou dossier de ce type
$ echo $?
2 # code de la dernière commande exécutée, ici ls
On peut aussi sortir immédiatement d'un script avec un code de terminaison n
en faisant : exit n
$ cat essai-exit.sh
#! /bin/bash
echo 1
exit 5 # ls script s'arrête ici
echo 2
$ ./essai-exit.sh
1
$ echo $?
5
Les commandes true
et false
ne font rien, mais renvoient un code de
terminaison de succès (0) ou d'échec (1).
$ false
$ echo $?
1
Les séparateurs &&
et ||
exploitent le code de terminaison :
$ cmd1 && cmd2 # exécute cmd1 ; si cmd1 réussi, exécute ensuite cmd2
$ cmd1 || cmd2 # exécute cmd1 ; si cmd1 échoue, exécute ensuite cmd2
Exemple :
$ mkdir toto && cd toto && touch truc && ls -l truc
éviter d'utiliser
&&
et ||
ensemble, ce n'est pas un
if then else
:
$ true && false || echo "raté"
raté
On peut inverser le résultat d'une commande :
$ true || echo bingo
$ ! true || echo bingo # true réussit, donc ! true échoue
bingo
6.4. Arguments
Lorsqu'on invoque un script avec des arguments, par exemple
$ ./monscript.sh bonjour les amis
les arguments du script (ici : "bonjour"
, "les"
, "amis"
) sont accessibles dans
le script par les paramètres positionnels $1
, $2
, ... :
$0
le nom du script avec son chemin d'appel$1
le premier argument ("bonjour"
)$2
le deuxième argument ("les"
)- ...
${10}
le dixième- ...
On a aussi
$#
le nombre d'arguments (entier)${!#}
le dernier argument
La liste complète des arguments peut être obtenue de deux manières :
$*
s'expanse en :$1 $2 $3 ...
"$@"
s'expanse en :"$1" "$2" "$3" ...
autrement dit, "$@"
protège les arguments d'un redécoupage sur les blancs
(on y reviendra plus tard).
Exemple :
$ cat ex2.sh
#! /bin/bash
echo "Je suis $0"
echo "J'ai reçu $# arguments."
echo "Le premier est : $1"
echo "Le deuxième est : $2"
echo "La liste complète est : $*"
$ ./ex2.sh le canal du midi
Je suis ./ex2.sh
J'ai reçu 4 arguments.
Le premier est : le
Le deuxième est : canal
La liste complète est : le canal du midi
La commande shift
shift
décale les arguments de 1 rang vers la gauche (le 1er est perdu).
shift n
décale les arguments de n rangs vers la gauche (les n
1ers perdus).
Exemple :
$ cat ex3.sh
#! /bin/bash
echo "Mes arguments sont : $*"
shift
echo "Après shift, mes arguments sont : $*"
shift 3
echo "Après shift 3, mes arguments sont : $*"
echo "Le premier argument est maintenant $1, et leur nombre est $#"
$ ./ex3.sh do ré mi fa sol la si
Mes arguments sont : do ré mi fa sol la si
Après shift, mes arguments sont : ré mi fa sol la si
Après shift 3, mes arguments sont : sol la si
Le premier argument est maintenant sol, et leur nombre est 3
La commande set (encore !)
La commande set
permet de modifier les arguments, y compris dans le shell
interactif :
$ set la Provence verte
$ echo $#
3
$ echo $*
la Provence verte
$ shift
$ echo $1
Provence
Pour éviter que set
interprète un argument (commençant par un -
) comme
une de ses options, il suffit de placer avant --
:
$ set -- -a -b -c
$ echo $*
-a -b -c
7. Structures de contrôle du shell
Le langage bash fournit des structures de contrôle :
-
des tests :
test .. [ .. ] [[ .. ]] (( .. ))
-
des branchements : (syntaxe
sh
héritée du langage Algol 68)if .. then .. fi if .. then .. else .. fi if .. then .. elif .. else .. fi case .. in .. esac select .. in .. do .. done
-
des boucles :
for .. do .. done for .. in .. do .. done for ((..)) do .. done while .. do .. done until .. do .. done
Comme tout ce que l'on voit ici, elles sont utilisables dans un script comme dans un shell interactif.
7.1. Le branchement if
Syntaxe :
if cmd1 ; then bloc1 ; fi
ou encore, en remplaçant les ;
par des retours chariots :
if cmd1
then bloc1
fi
if cmd1 ; then # variante
bloc1
fi
La commande cmd1
est exécutée ; si elle réussit, le bloc de commandes bloc1
est exécuté. C'est équivalent à :
cmd1 && bloc1
Exemple :
if mkdir toto ; then
echo "Création de toto/ réussie"
cd toto
fi
Variantes : (avec ;
ou retour chariot avant then
, else
, elif
et fi
)
if cmd1 ; then
bloc1 # exécuté si cmd1 a réussi
else
bloc2 # exécuté si cmd1 a échoué
fi
if cmd1 ; then
bloc1
elif cmd2 ; then # elif signifie else if
bloc2
elif ...
...
else
bloc_n
fi
(voir exemples section suivante)
7.2. Les tests
Le shell bash dispose d'au moins trois opérateurs de test :
- la commande interne
test
- la commande
[
, clôturée par un]
- la commande
[[
, clôturée par un]]
Ces opérateurs effectuent un test selon des arguments, puis réussissent ou
échouent (code de terminaison 0 ou != 0).
Ils sont donc utilisables dans un if
, avec &&
ou ||
.
Au niveau de la syntaxe, il faut toujours
- insérer des espaces entre les arguments, et à l'intérieur des crochets ;
- protéger les chaînes de caractères avec des
""
.
La commande test
La commande test
opère sur (voir : help test
)
-
des fichiers :
test -e file # file existe test -f file # file est un fichier régulier test -d file # file est un répertoire test -L file # file est un lien test -r file # l'utilisateur a le droit de lire file test -w file # d'écrire file test -x file # d'exécuter file test -N file # file modifié depuis sa dernière lecture test file1 -nt file2 # file 1 est plus récent que file2 ...
Exemple :
g="toto" if test -f "$g" ; then echo "$g est un fichier régulier" elif test -d "$g" ; then echo "$g est un répertoire" else echo "$g n'est ni un fichier régulier ni un répertoire" fi
-
des chaînes de caractère :
test -z str # str est vide test -n str # str est non vide test str1 = str2 # chaînes égales (bash accepte "==" mais pas sh) test str1 != str2 # chaînes différentes test str1 \< str2 # str1 avant str2 dans l'ordre lexicographique test str1 \> str2 # str1 après str2
-
des entiers :
test x -eq y # x == y (equal) test x -ne y # x != y (not equal) test x -lt y # x < y (less than) test x -le y # x <= y (less or equal) test x -gt y # x > y (greater than) test x -ge y # x >= y (greater or equal)
Exemple :
if test $# -eq 0 ; then echo "il n'y a pas d'argument" fi
-
divers :
test -v var # la variable var existe test -o opt # l'option du shell est définie test ! ... # inverse le résultat du test test expr1 -a expr2 # et logique entre les 2 expressions test expr1 -o expr2 # ou logique entre les 2 expressions test \( expr \) .. # parenthèses de priorité
L'opérateur [ ]
L'opérateur crochets [ .. ]
est identique à la commande test ..
, et
vient de sh
(voir : help [
) ; les lignes suivantes sont équivalentes :
$ test -z "$a" && echo "a est vide"
$ [ -z "$a" ] && echo "a est vide"
$ if test -z "$a" ; then echo "a est vide" ; fi
$ if [ -z "$a" ] ; then echo "a est vide" ; fi
Remarque :
$ type -a [
[ est une primitive du shell
[ est /usr/bin/[
L'opérateur [[ ]]
L'opérateur double crochets [[ .. ]]
est propre à bash : même syntaxe que
test
et [ .. ]
, avec en plus (voir : help [[
) :
-
des tests sur des motifs (à droite, sans
""
) :$ [[ "bonjour" == bon* ]] ; echo $? 0
-
des tests sur des expressions régulières (à droite, sans
""
) :$ [[ "bonjour" =~ ^bon.*$ ]] ; echo $? 0
-
utilisation des opérateurs
&&
(au lieu de-a
),||
(au lieu de-o
), des parenthèses(
et)
(au lieu de\(
et\)
).