L'entête d'un script shell devrait toujours commencer par un sha-bang qui assurera le chargement du bon shell.
Shebang du shell bash:
#!/bin/bash
$ echo $0
bash
$ echo $SHELL
/bin/bash
$ cat /proc/$$/cmdline
bashuser@computer:~$
$ `cat /proc/$$/cmdline` --version
GNU bash, version 4.2.10(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
Licence GPLv3+ : GNU GPL version 3 ou ultérieure <http://gnu.org/licenses/gpl.html>
Un simple commentaire
# ceci est un commentaire
Lancer bash en mode débogage avec le nom du script en paramètre
$ bash -x deboge_moi.sh
Avant de pouvoir exécuter le script il faut que le fichier soit rendu exécutable
# chmod +x script.sh
Et pour l'exécuter il faut être dans le même répertoire
$ ./script.sh
Ou taper le chemin complet
$ /home/user/script.sh
Pour exécuter le script depuis n'importe où, exactement comme une commande, il faut le copier dans un des répertoires $PATH du système.
On peut connaitre les répertoires $PATH grâce à la commande echo $PATH
ou echo $PATH | tr : \\n
.
$ echo $PATH | tr : \\n
/usr/local/bin
/usr/bin
/bin
/usr/local/games
/usr/games
Les répertoires listés ci-dessus sont communs à tous les utilisateur du système, y copier un script le rendra accessible à tous. C'est peut-être ce que vous souhaitez mais dans la pratique il est plus commode de disposer d'un répertoire dédié à ce genre de travail dans son espace personnel, un répertoire "/home/user/bin" par exemple.
Pour ajouter ce répertoire "/home/user/bin" à la variable $PATH on procédera ainsi:
$ export PATH="$HOME/bin:$PATH"
Toute-fois cette commande est provisoire et devra être répétée chaque fois que vous fermez le terminal. Pour y remédier il faudra enregistrer cette commande dans le fichier caché .bashrc qui est sensé se trouver dans votre répertoire personnel. Ceci fait vous devez recharger ce fichier de configuration avec la commande $ source ~.bashrc
ou fermer et rouvrir le terminal.
Voila il ne reste plus qu'à copier votre script dans ce répertoire pour y avoir accès d'où vous voulez.
$ cp script.sh /home/user/bin/script.sh
Il suffira ensuite de l'appeler le plus simplement du monde
$ script.sh
l'extension .sh n'est pas obligatoire, il est donc possible de copier le script
$ cp script.sh /home/utilisateur/bin/script
et de l'appeler encore plus simplement
$ script
comme n'importe quelle autre commande.
La syntaxe est
variable='valeur'
Et pour l'afficher
echo $variable
La valeur de la variable doit être entourée de "quotes", il en existe 3 sortes:
- Les apostrophes ' ' (simples quotes)
- Les guillemets " " (doubles quotes)
- Les accents graves ` ` (back quotes)
Selon les "quotes" utilisés le comportement de bash sera différent
Avec des simple quotes les variables ne sont pas interprétés
var='ceci est du texte'
echo 'la valeur est : $var'
Affichera en console
$ la valeur est : $var
Avec des doubles quotes le contenu est interprété
var='ceci est du texte'
echo "la valeur est : $var"
Affichera en console
$ la valeur est : ceci est du texte
Avec les back quotes la valeur de la variable est exécutée
var=`pwd`
echo "vous êtes ici : $var"
Affichera en console
$ vous êtes ici : /home/user/
Dans ce cas de figure préférez plutôt cette méthode:
echo "vous êtes ici $(pwd)"
Les saut de lignes se font grâce à \n placé dans une chaîne de caractère mais pour qu'il soit interprété comme tel l'option -e doit être activée
$ echo -e "ligne 1 \n ligne 2"
Les variables d'environnement sont des variables accessibles à tout moment.
Afficher les variables d'environnement
$ env
ORBIT_SOCKETDIR=/tmp/orbit-manu
SSH_AGENT_PID=1545
GIO_LAUNCHED_DESKTOP_FILE_PID=5845
GPG_AGENT_INFO=/tmp/keyring-ImY6lo/gpg:0:1
TERM=xterm
SHELL=/bin/bash
XDG_SESSION_COOKIE=abc0a91a3425c3d8b2b42de90000000a-1323506409.211427-1853304978
WINDOWID=69206020
GNOME_KEYRING_CONTROL=/tmp/keyring-ImY6lo
GTK_MODULES=canberra-gtk-module:canberra-gtk-module
...
Quelques explications:
- SHELL : indique quel type de shell est en cours d'utilisation (sh, bash, ksh...)
- PATH : la liste des répertoires qui contiennent les programmes ou scripts accessibles depuis n'importe ou
- EDITOR : l'éditeur de texte par défaut qui s'ouvre lorsque cela est nécessaire.
- HOME : la position du dossier home.
- PWD : le dossier dans lequel on se trouve.
- OLDPWD : le dossier dans lequel on se trouvait auparavant.
Pour s'en servir il suffit de les appeler par leur nom
echo "l'éditeur par défaut sur cet ordinateur est $EDITOR"
Les scripts peuvent accepter des paramètres:
$ ./script.sh param1 param2 param3
Récupérer ces paramètres dans le script
#!/bin/bash
echo "Vous avez lancé $0, il y a $# paramètres"
echo "Le paramètre 1 est $1"
si on lance le script avec des paramètres cela affichera
$ ./script.sh param1 param2 param3
Vous avez lancé ./script.sh, il y a 3 paramètres
Le paramètre 1 est param1
Quelques explications:
- $# : contient le nombre de paramètres
- $0 : contient le nom du script exécuté
- $1 : contient le premier paramètre
- $2 : contient le second paramètre
- ...
- $9 : contient le 9 ème paramètre
Seule 9 paramètres peuvent êtres entrés mais si toutefois il y en avait plus il est possible de les décaler un à un.
Utiliser shift pour décaler les paramètres:
#!/bin/bash
echo "Le paramètre 1 est $1"
shift
echo "Le paramètre 2 est $1"
...
à l'affichage
$ ./script.sh param1 param2 param3
Le paramètre 1 est param1
Le paramètre 2 est param2
...
Supposons que l'on dispose d'un script contenant plusieurs tableaux sous cette forme.
jp=('Jean-Perre' 'Dupont' 'jeanpierre@mail.com' '01.18.17.16.15')
mimil=('Emile' 'Laporte' 'milo@mail.com' '04.28.27.26.25')
...
Et que l'on souhaite appeler ce script avec comme paramètre le nom de variable d'un tableau comme ceci
./script.sh mimil
Afin de récupérer chacune des entrée du tableau il faudra obligatoirement passer par des références indirectes
nom=$1[0]
prenom=$1[1]
mail=$1[2]
tel=$1[3]
echo ${!nom}
echo ${!prenom}
echo ${!mail}
echo ${!tel}
affichera:
Emile
Laporte
milo@mail.com
04.28.27.26.25
Syntaxe:
read nomvariable
echo "la valeur est : $nomvariable"
Lors de l'exécution du script le prompt attendra une saisie de l'utilisateur et une fois validé affichera:
$ la valeur est : [ce que l'utilisateur aura saisi]
read var1 var2
echo "la saisie 1 est $var1 et la saisie 2 est $var2"
read assigne chaque mot saisi à une variable, si l'utilisateur entre plus de mots qu'il y a de variables c'est la dernière variable qui prendra tous les mots en trop.
Le paramètre -p
permet d'afficher un message de prompt pour que l'utilisateur sache quoi entrer
read -p 'entrez un mot: ' mot
echo "vous dites $mot"
Le paramètre -n limite le nombre de caractères
read -p 'Entrez un mot de 5 caractères maximum : ' -n 5 mot
echo -e "\n vous dites $mot"
Le paramètre -t limite le temps autorisé pour la saisie
read -p 'vous avez 5 secondes pour dire un mot : ' -t 5 mot
Le paramètre -s masque ce que l'utilisateur écrit
read -p 'Entrez votre mot de passe : ' -s motdepasse
Bash reconnait les variables comme des chaînes de caractères, il est donc nécessaire d'utiliser la commande let
pour faire des opérations.
L'addition:
let "a = 5 + 4"
La soustraction:
let "a = 5 - 4"
La multiplication:
let "a = 4 * 2"
La division:
let "a = 12 / 4"
Le reste de la division (modulo):
let "a = 12 % 4"
La puissance ou exposant:
let "a = 10 ** 2"
Il est également possible de concaténer les opérations
let "a = a * 4"
est équivalent à
let "a *= 4"
ici les retours sont des nombres entiers. Pour utiliser des décimaux il faut utiliser la commande
bc
La variable globale $RANDOM
génère un nombre aléatoire compris entre 0 et 32767
Ainsi si on tape dans un terminal:
$ echo $RANDOM
Affichera aléatoirement un nombre entre 0 et 32767.
Mais il est des situations ou l'on n'a pas 32768 possibilités et on aimerait bien obtenir un nombre aléatoire entre 0 et 20 par exemple.
Don't panic, imaginons que $RANDOM
nous retourne 23752
si on divise 23752 par 20 on obtient 1187,6 et si à l'inverse on multiplie 1187 * 20 on obtient 23740.
En d'autre terme il y à 1187 fois le nombre 20 dans 23752 et il reste 12.
Vous l'aurez compris c'est le reste de la division que l'on va garder qui ne sera jamais supérieur à 19 et vaudra au minimum 0.
Donc si on souhaite un nombre compris entre 0 et 20 on écrira dans un shell:
$ echo $[$RANDOM % 21]
et pour un nombre entre 1 et 20 on fera simplement
$ echo $[$RANDOM % 20 + 1]
En mathématique la multiplication et la division sont prioritaire sur l'addition et la soustraction donc "$RANDOM % 20 + 1" équivaut à écrire "($RANDOM % 20) + 1".
Si on souhaite un nombre compris entre 30 et 100 on fera alors:
$ echo $[$RANDOM % (101 - 30) + 30]
Pour automatiser un peu les choses on pourrait dans un script bash écrire ceci:
min=30
max=100
n=$[($RANDOM % ($[$max - $min] + 1)) + $min]
echo $n
et l'adapter aux besoins simplement en changeant min et max
min=0
max=9
n=$[($RANDOM % ($[$max - $min] + 1)) + $min]
echo $n
Déclaration d'un tableau
tableau=('var1' 'var2' 'var3')
Accéder à une valeur du tableau
${tableau[2]}
/!\ La clé des tableau commence à 0 donc dans l'exemple ${tableau[2]} vaut var3 /!\
Incrémenter le tableau d'une valeur supplémentaire
tableau[3]='var4'
Afficher les valeurs de tout un tableau
echo ${tableau[*]}
Affichera
var1 var2 var3 var4
Compter le nombre d'éléments d'un tableau
tab_tld=('.com' '.net' '.org' '.fr' '.ch' '.be')
n=${#tab_tld[*]}
If then syntaxe 1
if [ condition ]
then
echo "ok"
fi
If then syntaxe 2
if [ condition ]; then
echo "ok"
fi
/!\ les espaces avant et après la condition [ condition ] sont indispensables /!\
Un exemple concret
#!/bin/bash
var='bob'
if [ $var = "bob" ]; then
echo "je suis d'accord"
fi
Si la condition est fausse le script n'exécute pas le echo
If then else
#!/bin/bash
var='bob'
if [ $var = "bob" ]; then
echo "je suis d'accord"
else
echo "je ne suis pas d'accord"
fi
If then elif else
#!/bin/bash
var='bob'
if [ $var = "bob" ]; then
echo "je suis d'accord"
elif [ $var = "boby" ]; then
echo "je suis moyennement d'accord"
else
echo "je ne suis pas d'accord"
fi
Autre méthode pour tester si des conditions sont justes mais avec une syntaxe plus lisible
#!/bin/bash
case $varAtester in
"Bruno")
echo "Salut Bruno"
;;
"Michel")
echo "Salut Michel"
;;
"Jean")
echo "Salut Jean?"
;;
*)
echo "Je ne sais pas qui vous êtes!"
;;
esac
/!\ Il est possible de faire des tests de condition partiels ainsi: case $varAtester in "B*") testera si $varAtester commence par un B majuscule, peu importe le reste du mot
Tout comme if then, Case peut tester plusieurs conditions:
#!/bin/bash
case $varAtester in
"Chien" | "Chat" | "Souris")
echo "C'est un mammifère"
;;
"Moineau" | "Pigeon")
echo "C'est un oiseau"
;;
*)
echo "Je ne sais pas ce que c'est"
;;
esac
Il est possible de faire des tests sur:
Test d'égalité
if [ $chaine1 = $chaine2 ]
if [ $chaine1 == $chaine2 ]
Test la différence
if [ $chaine1 != $chaine2 ]
Test si la chaîne est vide
if [ -z $chaine ]
Test si la chaîne est non vide
if [ -n $chaine ]
Test d'égalité (equal)
if [ $nbr1 -eq $nbr2 ]
Test la différence (non equal)
if [ $nbr1 -ne $nbr2 ]
Test l'infériorité (lower than)
if [ $nbr1 -lt $nbr2 ]
Test l'infériorité ou l'égalité (lower or equal)
if [ $nbr1 -le $nbr2 ]
Test la supériorité (greater than)
if [ $nbr1 -gt $nbr2 ]
Test la supériorité ou l'égalité (greater or equal)
if [ $nbr1 -ge $nbr2 ]
Test si le fichier existe
if [ -e $fichier ]
Test si le fichier est un répertoire
if [ -d $fichier ]
Test si le fichier est un fichier
if [ -f $fichier ]
Test si le fichier est un lien symbolique
if [ -L $fichier ]
Test si le fichier est en lecture
if [ -r $fichier ]
Test si le fichier est en écriture
if [ -w $fichier ]
Test si le fichier est exécutable
if [ -x $fichier ]
Teste si fichier1 est plus récent que fichier2 (newer than)
if [ $fichier1 -nt $fichier2 ]
Teste si fichier1 est plus vieux que fichier2 (older than)
if [ $fichier1 -ot $fichier2 ]
Il est à noter que bash test si le fichier existe
condition ET condition
if [ $# -ge 1 ] && [ $1 = 1 ]
condition OU condition
if [ $# -ge 1 ] || [ $1 = 1 ]
if [ $1 != 1 ]
While : tant que la condition est vérifié, la boucle répète l'instruction do
while [ condition ]
do
echo 'on est dans la boucle'
done
Tout comme if, on peut écrire le début de la condition sur une seule ligne grâce au ";"
while [ condition ]; do
echo 'on est dans la boucle'
done
Until : jusqu'à ce que la condition soit vérifiée, la boucle répète l'instruction do
until [ condition ]; do
echo 'on a pas fini la boucle'
done
La boucle "for" permet de parcourir une liste de valeurs et de boucler autant de fois qu'il y en a.
#!/bin/bash
for variable in 'valeur1' 'valeur2' 'valeur3'
do
echo "La variable vaut $variable"
done
Elle devient même très intéressante quand on la combine avec des commandes.
Ici la boucle liste le contenu du répertoire courant et affiche les fichiers
for fichier in `ls`
do
echo "Fichier trouvé : $fichier"
done
Ou mieux, on renomme chaque fichier grâce à la concaténation
for fichier in `ls`
do
mv $fichier $fichier-old
done
Simuler une boucle "for" classique:
Dans cet exemple, "seq" génère tous les nombres allant du premier paramètre au dernier paramètre, donc 1 2 3 4 5 6 7 8 9 10
for i in `seq 1 10`;
do
echo $i
done
La même chose mais en avançant de 2 en 2
for i in `seq 1 2 10`;
do
echo $i
done
echo -e "\<delimiteur>[<codes_couleurs_et_mise_en_forme>m <texte à mettre en forme> \<délimiteur_de_fin>
exemple: mettre une partie du texte en rouge
echo -e "\033[31m texte en rouge \033[0m ce texte n'est plus en rouge"
Quelques explications s'imposent:
couleur du texte | couleur du fond | couleur |
---|---|---|
30 | 40 | noir |
31 | 41 | rouge |
32 | 42 | vert |
33 | 43 | jaune |
34 | 44 | bleu |
35 | 45 | magenta |
36 | 46 | cyan |
37 | 47 | blanc |
Mettre du texte en jaune gras souligné sur fond rouge
echo -e "\033[1;4;41;33mCeci est le texte mis en forme\033[0m"
string="ceci est Une.chaine.Pour L'exemple"
echo ${string:0:1}
c
echo ${string:0:3}
cec
echo ${string:3:8}
i est Un
La première valeur numérique indique le caractère à partir du quel on commence à récupérer, la seconde indique le nombre de caractère à récupérer.
echo ${#string}
34
Le premier caractère en majuscule
echo ${string^}
Ceci est Une.chaine.Pour L'exemple
La même chose avec sed
echo $string | sed 's/\(.\)/\U\1/'
echo ${string^^}
CECI EST UNE.CHAINE.POUR L'EXEMPLE
La même chose avec tr
echo $string | tr [:lower:] [:upper:]
${string,}
ceci est Une.chaine.Pour L'exemple
string à déjà son premier caractère en minuscule mais faites moi confiance, ça marche!
La même chose avec sed
echo $string | sed 's/\(.\)/\L\1/'
echo ${string,,}
ceci est une.chaine.pour l'exemple
La même chose avec tr
echo $string | tr [:upper:] [:lower:]
echo $string | tr -d ${string:0:1}
eci est Une.chaine.Pour L'exemple
echo $string | tr -d ${string:20:5}
ceci est Une.chaine. L'exemple
Supprimer les espaces blanc
echo $string | tr -d [:blank:]
ceciestUne.chaine.PourL'exemple
On peut remplacer [:blank:] par un caractère quelconque. Par exemple:
echo $string | tr -d .
ceci est UnechainePour L'exemple
echo $string | tr -d c
ei est Une.haine.Pour L'exemple
echo $string | tr . ' '
ceci est Une chaine Pour L'exemple
var=`echo ${string^^}`
echo $var
CECI EST UNE.CHAINE.POUR L'EXEMPLE
$ myvar=abc_def_ghi
$ echo ${myvar%%_*}
abc
$ thefile=DSC0123-987.jpg
$ echo ${thefile%%-*}
DSC0123
Partie finissante (suffix)
$ echo ${thefile##*.}
jpg
Retirer le suffix
$ thefile=DSC0123.abc.jpg
$ echo ${thefile%.*}
DSC0123.abc
Quand on à dans notre code une répétition d'instructions il peut être intéressant de les placer dans une fonction.
La syntaxe de la déclaration d'une fonction se fait ainsi:
maFonction() {
# code ...
}
et on l'appèle ainsi:
maFonction param1 param2 ...
Prenons un exemple concret avec les nombres aléatoires cité plus haut.
On avait la suite d'instructions suivante qui permet de générer un nombre aléatoire entre 0 et 9
min=0
max=9
n=$[($RANDOM % ($[$max - $min] + 1)) + $min]
echo $n
On pourrait transformer tout cela comme ça:
random() {
n=$[($RANDOM % ($[$2 - $1] + 1)) + $1]
}
random 0 9
echo $n
random 50 100
echo $n
Il faut savoir que dans les fonction, les variables sont globales à l'ensemble du script, d'ailleurs comme on peut le voir dans l'exemple ci-dessus, la variable $n n'est déclarée que dans la fonction mais appelée depuis l'extérieur.
Il est possible de déclarer une variable local en faisant précéder son nom du mot clé local. Il est donc possible d'écrire la même fonction ainsi:
random() {
local plus1=1
n=$[($RANDOM % ($[$2 - $1] + $plus1)) + $1]
}
La fonction date retourne comme on peut s'y attendre la date du jour, mais le format dépendra de votre distribution et de la façon dont elle est paramétrée.
date
mar. 18 mai 2021 08:45:56 CEST
Ici on peut s'apercevoir que l'impression de retour est en français et qu'il s'agit de l'heure qu'il est en Europe centrale (CEST – Central European Summer Time). Toutefois si vous avez besoin d'utiliser une date dans un script bash vous aurez besoin d'un format plus strict et stable sur lequel vous pourrez vous appuyer.
Si vous pratiquez déjà un langage de programmation et que vous avez eu à manipuler les dates vous savez ce que représentent les symboles d m y h m s ..., pour ceux qui l'ignorent il s'agit de signes interprétables par la fonction date
dans le but d'afficher une valeur du temps actuel dans un format précis. Par exemple date +"%Y"
affichera l'année en cours à 4 chiffres. Les pages de man en dressent la liste complète.
Avant de vous livrer quelques exemples en vrac on notera simplement que pour afficher la date en console un simple date "+%d-%m-%Y"
suffira à afficher 18-05-2021
mais dans un script il convient d'utiliser echo
ou de stocker le retour de la commande dans une variable tel que ci-dessous:
DATE=$(date +"%d-%m-%Y")
Libre à vous par la suite d'utiliser la variable $DATE
comme vous l'entendez.
date "+%d-%m-%Y %H:%M"
18-05-2021 08:45
echo `date "+%d-%m-%Y %H:%M"`
18-05-2021 08:45
echo `date -d tomorrow "+%d-%m-%Y %H:%M"`
19-05-2021 08:45
echo `date -d "yesterday 12:15" "+%d-%m-%Y %H:%M"`
17-05-2021 12:15
echo `date -d "yesterday -1 months" +"%d-%m-%Y %H:%M"`
17-04-2021 08:45
echo `date -d "+1 months -3 days" "+%d-%m-%Y %H:%M"`
15-06-2021 08:45
echo `date +"%d-%m-%Y" -d '1 day ago'`
17-05-2021
echo `date -d "this monday" "+%d-%m-%Y"`
24-05-2021
echo `date -d "next mon" "+%d-%m-%Y"`
24-05-2021
echo `date -d "last mon" "+%d-%m-%Y"`
17-05-2021
echo `date --date="this tuesday" "+%d-%m-%Y"`
18-05-2021
echo `date --date="next tue" "+%d-%m-%Y"`
25-05-2021
echo `date --date="last tue" "+%d-%m-%Y"`
11-05-2021
echo `date +"%s"`
1621320300
echo `date -d '@1621320300' +"%d-%m-%Y %H:%M"`
18-05-2021 08:45
J'attire votre attention sur le fait que le symbole + peut être placé indifféremment à l'intérieur ou à l'extérieur des guillemets.
La particularité quand on boucle sur le retour d'une commande c'est que ce sont les espaces qui délimitent les éléments.
for element in `ls`; do echo $element; done
un
repertoire
un
fichier
On préférera la boucle while et commande read qui marquera chaque élément trouvé par ls.
ls | while read element; do echo $element; done
un repertoire
un fichier
On préférera également entourer les variables du shell de guillemets ce qui évitera des messages d'erreur du genre "la cible ' . . . ' n'est pas un répertoire". Dans lexemple ci-dessous on cherche à renommer les éléments avec un préfixe.
ls | while read element; do mv "$element" "prefixe - $element"; done
OLDIFS=$IFS;
IFS=$(echo -en "\n\b" );
for i in `ls`;
do
# code ...
done;
IFS=$OLDIFS;
Pour en savoir plus à propos de $IFS : Le séparateur standard du shell
string="chaîne ou doit se faire le test"
regex=`expr match "$string" '\(^[a-zA-Z].*\)'`
if [ "$string" != "$regex" ]
then
echo "syntaxe error"
else
echo "syntaxe ok"
fi;
dans l'exemple (^[a-zA-Z].*) test si la chaîne commence par une lettre
Quand dans php on demande à afficher la chaîne toto en md5
<?php echo md5('toto'); ?>
On obtient ça: f71dbe52628a3f83a77ab494817525c6
Mais quand on demande la même chose à bash dans une console
$ echo "toto" | md5sum
On obtient ça: 11a3e229084349bc25d97e29393ced1d
Alors, bash ne saurait-il pas "hasher" en md5?
En fait bash hash la chaîne plus un saut de ligne ("toto\n")
On contourne facilement le problème en demandant à bash de hasher la chaîne avec php
$ echo '<? echo md5("toto");?>' | php
Ou en supprimant le saut de ligne grâce à l'option -n
$ echo -n "toto" | md5sum
On obtient donc dans ces deux cas: f71dbe52628a3f83a77ab494817525c6
Faire des recherches sur