Scripting ShellBash – comment Automatiser votre système Linux

Les bases du scripting Shell :
Permet à ceux qui n’ont jamais développé de pouvoir comprendre le fonctionnement du scripting et d‘être capable de mettre en place leurs premiers scripts grâce à une méthode pas à pas et à quelques exercices et quizz permettant d’asseoir les connaissances acquises. Vous y verrez notamment à quoi correspond le Shebang, comment exécuter vos scripts, et qu’est-ce que sont les variables. Vous mettrez ensuite en place vos premiers tests avec les conditions If et les boucles For et While. Vous pourrez demander à l’utilisateur de saisir des données que votre script sera capable d’interpréter et d’utiliser par la suite…

Les scripts shell – Instructions de contrôle

Le scripting Shell intermédiaire

Permettra d’utiliser à bon escient les codes erreurs (exit code) et les fonctionnalités accessibles grâce à l’utilisation du ET (&&) et du OU (||). Vous mettrez ensuite en place vos premières fonctions notamment lors d’un exercice vérifiant l’existence d’un fichier, et vous utiliserez les wildcards (aussi appelés méta-caractères) pour rendre vos scripts beaucoup plus puissants !

Le scripting avancé :

Vous donnera les outils pour utiliser du code plus performant, par exemple grâce au case. Vous serez également capable de générer vos propres logs et d’utiliser le langage YAML pour les fichiers de configuration. Enfin vous en verrez d’avantages sur les variables (unsetexporttypesetreadonly, etc…) et sur la commande sed.

Un script

  • Contient une série de commandes.
  • Ces commandes sont exécutées par un interpréteur (le shell pour les scripts que nous allons voir) les unes après les autres.
  • Tout ce que vous pouvez taper en ligne de commande peut être inclus dans un script.

Rendre un script exécutable

Il est important de mettre les droits sur le fichier contenant le script de manière à ce que celui-ci devienne exécutable :

chmod a+x script.sh

Exécuter un script

Pour exécuter un script, vous avez le choix de, soit vous trouver dans le répertoire où se situe votre script, soit d’utiliser le chemin absolu.

# Chemin absolu : 
/home/jordan/script.sh 
 
# Chemin relatif (si je suis dans le même répertoire que le script): 
./script.sh

Le Shebang !

Au début de chaque script bash, il est important de faire figurer le shebang :

#!/bin/bash
# Remarque : Il en existe d'autres
#!/bin/sh
#!/bin/csh 
#!/bin/zsh
#!/usr/bin/python

Les variables

  • Les variables sont sensibles à la casse et par convention, on les met toujours en majuscules.
  • Attention aux nom de variables : les variables ne peuvent contenir que des underscores, majuscules, minuscules et chiffres (avec la condition de ne pas commencer par un chiffre)
  • Attention à ne pas mettre d’espace entre la variable, le signe = et les « .
  • Pour utiliser les variables et afficher le contenu associé, il faut faire précéder le nom de la variable par un $.
  • Lorsque vous souhaitez inclure une variable dans un mot par exemple, vous pouvez utiliser les { }
#!/bin/bash
PRENOM="Robert"
NOM="CHART"
echo "Bonjour $PRENOM ${NOM}, bienvenue !"
./script.sh
Bonjour Robert CHART, bienvenue !
# Remarque : sans les accolades, la virgule après NOM aurait engendré une erreur.

Lorsque vous souhaitez inclure une variable dans un mot par exemple, vous pouvez utiliser les { }

Assigner les sorties de commande à une variable :

  • il faut mettre la variable entre parenthèses
  • ou utiliser la back cote ( ` : alt+7) à la place de $( )
./script.sh
echo ${MACHINE}! # renvoi debian!
LISTE=`ls -l`
echo $LISTE # renvoi le résultat de la commande ls

Les tests

Lorsque vous tapez une commande, vous pouvez prendre le temps d’analyser la réponse du système et prendre une décision en fonction de cette réponse. Il est tout à fait possible d’effectuer les mêmes étapes avec le scripting Shell grâce aux tests dont la syntaxe est la suivante :

[ voici-la-condition-du-teste-a-verifier ] ( il est important de respecter les espaces après le [ et avant le ] )

./script.sh
[ -e /home/jordan/bonjour ]
  • La commande nous retourne la valeur 0 (True) si le fichier existe
  • La commande nous retourne la valeur 1 (False) si le fichier n’existe pas

Pour avoir la liste des tests possibles taper : « help test »
Author Name

Parmi les opérateur principaux nous avons :
• -e : 0 (True) si le fichier existe
• -d : 0 (True) s’il s’agit d’un dossier
• -r : 0 (True) si le fichier est disponible en lecture pour l’utilisateur
• -s : 0 (True) si le fichier existe et n’est pas vide
• -w : 0 (True) si le fichier est disponible en écriture pour l’utilisateur
• -x : 0 (True) si le fichier est disponible en exécution pour l’utilisateur

#!/bin/bash
PRENOM="Robert"
NOM="CHART"
[ -z $PRENOM ]
echo $?
—>./script.sh
—>1
[ -n $PRENOM ]
echo $?
—>./script.sh
—>0
[ $PRENOM = $NOM ]
echo $?
—>./script.sh
—>0
Ne pas oublier de mettre des espaces avant et après le signe =
Le test d’égalité renvoie 0 si les deux chaînes sont identiques et 1 si elles ne le sont pas
Si l’on veut tester la différence on utilise !=
Author Name

Les tests sur les chiffres

De la même manière on il est possible de comparer deux nombres entre eux.
chiffre1 -eq chiffre2 : 0 si chiffre1 est égal à chiffre2
Pareil avec : -ne, -lt, -le, -gt, -ge

• chiffre1 –eq chiffre2 : 0 si chiffre1 est égal à chiffre2
• chiffre1 –ne chiffre2 : 0 si chiffre1 est différent de chiffre2

• chiffre1 –lt chiffre2 : 0 si chiffre1 est plus petit que chiffre2
• chiffre1 –le chiffre2 : 0 si chiffre1 est plus petit ou égal que
chiffre2

• chiffre1 –gt chiffre2 : 0 si chiffre1 est plus grand que chiffre2
• chiffre1 –ge chiffre2 : 0 si chiffre1 est plus grand ou égal que
chiffre2

Le code retour d’une commande

  • En général, la valeur de retour d’une commande lorsqu’elle c’est exécuté correctement est 0.
  • Ne pas hésiter à se servir du manuel (commande man) pour prendre connaissance de l’utilisation détaillée d’une commande.
  • Le code retour d’une fonction est disponible avec $? (echo $?)
#!/bin/bash

HOTE=$1 
NOMBRE_DE_PAQUETS=$2 
ping –c $NOMBRE_DE_PAQUETS $HOTE

if [ "$?" –ne "0" ]
then 
 echo "L'hote $HOTE n'est pas joignable"  
else 
 echo "L'hote $HOTE est joignable" 
fi 

Créer son propre code retour :

  • Il est tout à fait possible de dire à votre script que dans certaines conditions, il quitte avec un code erreur différent de 0.
  • En effet, s’il s’est correctement exécuté, il sortira avec un code égal à 0 (ou égal à celui de la dernière commande exécutée), mais vous pouvez lui spécifier un code différent avec la commande :
    exit 1
    exit 2
  • Attention, car dés que la commande exit est utilisée, le script s’arrête.

Les conditions

Les if / elif / else

if [ condition-est-vraie ] 
then 
  command 
elif [ condition-est-vraie ]
then
  command
else  
  command 
fi 
#!/bin/bash
CHIFFRE1='16'
CHIFFRE2='17'
if [ $CHIFFRE1 –lt $CHIFFRE2 ] 
then 
  echo "$CHIFFRE1 est plus petit que $CHIFFRE2"  
elif [ $CHIFFRE1 –gt $CHIFFRE2 ]
then 
  echo "$CHIFFRE1 est plus grand que $CHIFFRE2" 
else 
  echo "$CHIFFRE1 est égal à $CHIFFRE2" 
fi
./script.sh 
-> 16 est plus petit que 17

Le case (alternative au if)

case "$VARIABLE" in
    premier_cas)
	Command1
	Command2
	;;
    deuxieme_cas)
	Command1
	Command2
	;;
    *)
	Command autre
	exit 1
	;;
esac
case "$VARIABLE" in
    premier_cas)
	Command1
	Command2
	;;
    deuxieme_cas)
	Command1
	Command2
	;;
    *)
	Command autre
	exit 1
	;;
esac
case "$1" in
    start|START)
# ou [sS][tT][aA][rR][tT] )
        /etc/init.d/apache2 start  
        ;; 
    stop|STOP) 
# ou [sS][tT][oO][pP] )
        kill $(cat /var/run/apache2/apache2.pid) 
        ;; 
    *) 
        echo "Merci d'indiquer start ou stop" 
        exit 1 
        ;; 
esac

Les boucles

La boucle for

for VARIABLE in OBJET1 OBJET2 OBJET3 OBJETn
	Command1
	Command2
done 
CHIFFRES="10 11 12 13"
COULEURS="rouge vert noir bleu"
for CHIFFRE in $CHIFFRES
	echo "Chiffre : $CHIFFRE"
done
for COULEUR in $COULEURS
	echo "La couleur est : $COULEUR"
done
./script.sh 

La boucle while

while [ condition-est-vrai ]
do 
	Command1
	Command2
done
#!/bin/bash 
 
while [ -z $PRENOM ]
do
 read –p "Quel est votre prenom ?" PRENOM 
Done 
Echo "Votre prenom est $PRENOM"
./script.sh 
 
Quel est votre prenom ? 
Quel est votre prenom ? 
Quel est votre prenom ? Robert
Votre prenom est Robert

Les paramètres de position

Les paramètres de position stockent le contenu des différents éléments de la ligne de commande utilisée pour lancer le script.

  • Il en existe 10 : $0 jusqu’à $9
  • Le script lui-même est stocké dans la variable $0
  • Le premier paramètre est stocké dans la variable $1
  • Il est possible d’accéder à toutes les variables de position à partir du $1 grâce au caractère « $@« 
  • Il existe différents caractères intéressants à utiliser concernant les variables de positionnement.
    $# : récupère le nombre de paramètres (à partir du $1)
    $* : récupère la liste des paramètres
  • Pour accéder aux valeurs supérieures à 9, on peut utiliser la commande « shift » qui va décaler les valeurs des paramètres
En lançant le script :
./script.sh argument1 argument2
Alors
$0 contient script.sh
$1 contient argument1
$2 contient argument2
Remarque :
echo « le premier argument à pour valeur $1 »
echo « le second argument à pour valeur $2 »
Est équivalent à
echo « le premier argument à pour valeur $1 »
shift
echo « le second argument à pour valeur $1 »

Entrées utilisateurs

  • La commande read : elle permet d’accepter les données du STDIN (entrée standard), c’est à dire va permettre à l’utilisateur d’entrer des données.
  • Si l’on veut afficher du texte pour préciser à l’utilisateur quel genre de données nous souhaitons qu’il entre, on peut également utiliser l’option « -p ».
echo "Quel est votre prénom ?"
read PRENOM 
# Est égal à
read -p "Quel est votre prénom ?" PRENOM
Remarques :
– La plupart du temps l’entrée standard fait référence à des données que l’utilisateur va taper au clavier.
– Cependant cette entrée standard peut aussi provenir d’autres sources de comme l’output d’une autre commande.

Utilisation du && : opérateur AND (ET) logique
Utilisation du || : opérateur OR (OU) logique

  • Le && permet d’exécuter une deuxième commande uniquement lorsque la première a renvoyé un code erreur église à 0 (signifiant qu’elle s’est bien exécutée).
  • permet d’exécuter une deuxième commande uniquement lorsque la première a renvoyé un code erreur différent de 0 (signifiant qu‘elle ne s’est pas exécuté de la bonne manière).
remi@vmi000000:~$ sudo ping -c 1 8.8.8.8 && echo "L'HOTE EST JOIGNABLE"
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=59 time=4.68 ms
--- 8.8.8.8 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 4.675/4.675/4.675/0.000 ms
L'HOTE EST JOIGNABLE

remi@vmi000000:~$ sudo ping -c 1 www.sitebidon.fr || echo "L'HOTE EST INJOIGNABLE"
ping: www.sitebidon.fr: Name or service not known
L'HOTE EST INJOIGNABLE

Les fonctions

Il existe deux manières de coder une fonction :

# déclarer une fonction en utilisant le mot "function"
function ma-fonction() {
	Command1
	Command2
}

# Déclarer une fonction sans déclarer le mot "function" :
ma-fonction () {
	Command1
	Command2
}

# Pour appeler la fonction : retaper son nom sans les parenthèses
ma-fonction

Le passage des paramètres à une fonction

  • Le passage des paramètres fonctionne de la même manière que les scripts.
  • Le premier paramètre est stocké dans le $1, le second dans le $2, etc…
  • Attention, le $0 continue à faire référence au nom du script lui même.
#!/bin/bash 
function internet() { 
ping -c $1 $2
 
if [ $? -eq 0 ] 
then 
        echo "La connectivité vers internet est établie"
else
        echo "Pas de connectivité vers internet"
fi 
} 

Internet "1" "8.8.8.8"

Notions de variables globales et locales

  • les variables globales peuvent tout à fait être utilisées dans les fonctions à condition qu’elles été déclarées avant la fonction
  • les variables déclarées dans une fonction ne peuvent être utilisées qu’une fois que la fonction a été exécutée
  • Pour déclarer des variables locales (n’existes que dans la fonction) on utilise « local »
#!/bin/bash
VARIABLE="Jordan"
 
function demonstration() { 
  echo "La variable $VARIABLE est utilisable" 
  local VARIABLE_IN_FUNCTION="1" 
}
 
demonstration 
# VARIABLE_IN_FUNCTION n'est pas utilisable

Code retour d’une fonction

  • si le code retour n’a pas été défini, alors c’est le code retour de la dernière variable exécutée dans la fonction qui sera celui de la fonction par défaut
  • Le code retour d’une fonction est comprise entre 0 et 255
  • On peut y accéder grâce à la commande $?
#!/bin/bash
function internet_connectivity() {
  ping –c 1 8.8.8.8 && return 0 
} 

internet_connectivity

if [ $? –eq 0 ] 
then 
  echo "Connectivité vers internet" 
fi

Exercice d’application 1

Réaliser le script respectant les règles suivantes :

  • Script auquel on passe en argument une liste de fichiers.
  • Le script va d’abord vérifier que l’utilisateur a bien saisi des arguments au moment de lancer le script. Si ce n’est pas le cas, alors le script sort avec une erreur 2.
  • Le script va vérifier si ces fichiers existent ou n’existent pas et nous afficher la réponse à l’écran.
  • Les commandes doivent être intégrées à des fonctions. On souhaite deux fonctions distinctes, une pour la vérification du nombre d’arguments et l’autre pour la vérification de l’existence des
    fichiers.
#!/bin/bash

# Recuperation des noms des fichiers
FICHIERS=$@
NOMBRE_ARGUMENTS=$#

# Verification que l'utilisateur a bien saisi des arguments
function verification_arguments(){
    if [ $NOMBRE_ARGUMENTS -eq 0 ]
    then
        echo "Attention, vous n'avez pas saisi les noms de fichiers !"
        exit 2
    fi
}
# On verifie que le fichier passe en argument n'existe pas deja
function verification_fichier_existe(){
    for FICHIER in $FICHIERS
    do
        ls $FICHIER 2> /dev/null
        if [ $? -eq 0 ]
        then
            echo "Le fichier $FICHIER existe"
        else
            echo "Le fichier $FICHIER n'esiste pas"
        fi
    done
}
verification_arguments
verification_fichier_existe $FICHIERS
remi@vmi000000:~$ nano exercice1.sh
remi@vmi000000:~$ sudo chmod a+x exercice1.sh

remi@vmi000000:~$ ./exercice1.sh
Attention, vous n avez pas saisi les noms de fichiers !

remi@vmi000000:~$ ./exercice1.sh ess.txt
Le fichier ess.txt n esiste pas

remi@vmi000000:~$ ./exercice1.sh essai.txt
essai.txt
Le fichier essai.txt existe

Les WILDCARDS

  • il s’agit d’un caractère ou d’une chaîne de caractères qui vont être utilisés pour prendre un certain nombre de valeurs
  • On parle de Globbing wildcards lorsqu’ils vont matcher avec une liste de fichiers ou de répertoires
  • Ils peuvent être utilisés avec la plupart des commandes comme le ls ou le rm
[Classes de]
Caractères
Observation
*match avec tous les caractères (ou l’absence de caractères) qu’il n’y en ait qu’un seul ou plusieurs
?match avec un seul caractère
[…]Les classes de caractères : il est possible de créer des patterns de recherche très spécifiques en utilisant les classes de caractères. Le matching va se faire avec un ou plusieurs des caractères inclus entre les crochets de la classe.
Exemple : ls -l t[aio]t[ai] —> tata titi (sur tata titi tutu tuto)
!Exclusion d’un certain nombre de caractères
Exemple : ls -l t[aiou]t!u —> tata titi tuto
Exemple : ls -l t[!au]t[!u] —> titi toto
[x-y]*On peut également créer une plage de caractères grâce aux classes
Exemples :
[1-3]* : match avec tous les fichiers commençant par 1, 2 ou 3
a-c]* : match avec tous les fichiers commandant par a, b ou c
Classes de caractères prédéfinies
[[:alpha:]]Toutes les lettres de l’alphabet (minuscules et majuscules)
[[:alnum:]]Toutes les lettres de l’alphabet (minuscules et majuscules) ainsi que tous les chiffres (0 à 9)
[[:digit:]]Tous les chiffres de 0 à 9
[[:upper:]]Toutes les lettres de l’alphabet en majuscule
[[:lower:]]Toutes les lettres de l’alphabet en minuscule
[[:space:]]Tous les caractères d’espacement (espace, tabulation, nouvelle ligne, etc…)
ExemplesDans un dossier on les fichiers :
tata, titi, tutu, toto, 1robert, 2robert, rob, robert1, robert2, t1t1
ls -ls [[:digit:]]robert renvoi les fichiers 1robert et 2robert
ls -ls robert[[:digit:]] renvoi les fichiers robert1 et robert2
ls -ls t[[:alpha:]]t? renvoi les fichiers tata, titi, toto, tutu
ls -ls t[[:alnum:]]t? renvoi les fichiers tata, titi, toto, tutu, t1t1
\?Pour effectuer des recherches sur des fichiers qui comportent un caractère de type wildcard, il faut utiliser le caractère d’échappement anti-/ avant le wildcard
Exemple : on ne peut pas créer un fichier t?t? avec la commande touch (car le caractère ? est du type wildcard). Pour le faire, on devra entrer : touch t\?t\?
ls -l t?t? renvoi les fichiers tata, titi, toto, tutu, t1t1
ls -l t\?t\? renvoi le fichier t?t?

Exercice d’application 2

  • Faire un script prenant deux arguments :
  • Premier argument : nom d’un fichier
  • Deuxième argument : peut prendre plusieurs valeurs comme :
    • copy (copie le fichier dans le répertoire /tmp/script)
    • delete (supprime le fichier)
    • read (lire le fichier).
  • Il faudra créer une fonction qui vérifiera que le fichier rentré en argument existe bien, et utiliser les case pour les différentes possibilités.
#!/bin/bash

# On verifie que le fichier passe en argument existe
function verification_fichier_existe(){
    ls $1 2> /dev/null
    case $? in
        0)
            echo "Le fichier $FICHIER existe bien"
            return 0
            ;;
        *)
            echo "Le fichier $FICHIER n'esiste pas"
            return 1
            ;;
    esac
}

# Verifie si l'utilisateur a bien indique un nom de fichier
if [ -z $1 ] || [ -z $2 ] 
then
    echo "Attention a preciser un nom de fichier et l'action"
    echo "Actions disponibles : copy, delete, ou read"
    exit 1
fi

# Execution de la fonction de verification
verification_fichier_existe $1
if [ $? -ne 0 ] 
then
    exit 2
fi

# Actions a executer
case "$2" in
    copy)
        mkdir /tmp/script 2> /dev/null
        cp $1 /tmp/script
        if [ $? -eq 0 ] 
        then
            echo "Le fichier $1 a bien ete copie dans /tmp/script/"
        else
            echo "Une ereeur a eu lieu au moment de la copie"
            exit 3
        fi
        ;;
    delete)
        rm $1
        if [ $? -eq 0 ] 
        then
            echo "Le fichier $1 a bien ete supprime"
        else
            echo "Une ereeur a eu lieu au moment de la suppression"
            exit 4
        fi
        ;;
    read)
        cat $1
        if [ $? -eq 0 ] 
        then
            exit 0
        else
            echo "Une ereeur a eu lieu au moment de la lecture"
            exit 5
        fi
        ;;
esac
remi@vmi00:~$ ./exercice2.sh
Attention a preciser un nom de fichier et l'action
Actions disponibles : copy, delete, ou read

remi@vmi00:~$ ./exercice2.sh tutu read
Le fichier  n'esiste pas

remi@vmi00:~$ ./exercice2.sh toto read
toto
Le fichier  existe bien
Salut l'artiste

remi@vmi00:~$ ./exercice2.sh tutu copy
Le fichier  n'esiste pas

remi@vmi00:~$ ./exercice2.sh toto copy
toto
Le fichier  existe bien
Le fichier toto a bien ete copie dans /tmp/script/
remi@vmi00:~$ ls /tmp/script
toto

remi@vmi00:~$ ./exercice2.sh toto delete
toto
Le fichier  existe bien
Le fichier toto a bien ete supprime
remi@vmi00:~$ ls toto
ls: cannot access 'toto': No such file or directory

Premières configurations système

Premier contacts

Premier contacts

Comment fonctionne un VPS

Instalation de la commande sudo

remi ALL=(ALL) NOPASSWD:ALL
%sudo   ALL=(ALL:ALL) NOPASSWD:ALL
puis on sauvegarde(Ctrl+o)/quit(Ctrl+x)
# On termine en redémarrant le service sudo:
/etc/init.d/sudo restart

apt update, upgrade, dist-upgrade et full-upgrade : quelles sont les différences ? quel terme utiliser en fonction de se que l’on cherche à faire : Lire l’article suivant…
E t en prime un lien vers un article de l’hébergeur Contabo intitulé : « Debian et Ubuntu Upgrade »
Author Name

Une adresse IP qui identifie votre hôte sur le réseau où il est connecté.
La commande « ip address » ou « ip a » en raccourci, permet d’afficher les interfaces réseau avec les adresses IP associées.

La commande retourne l’interface « lo » qui correspond à l’interface de « loopback » (boucle locale), ainsi que l’interface « eth0 » correspondante à la carte réseau connectée à mon réseau local. On peut voir que l’adresse IP actuelle sur cette carte est « 38.242.201.85/20« .
Un masque de sous-réseau qui indique la partie de votre adresse qui caractérise le réseau local sur lequel votre hôte est connecté, et lui permet de déterminer, pour n’importe quelle adresse IP, si celle-ci fait ou non partie du réseau local
Une passerelle par défaut qui indique l’adresse IP à laquelle il faut transmettre les paquets IP destinés à des hôtes situés hors du réseau local, pour qu’ils soient routés vers le réseau local de leur destinataire.
Afin de connaître la passerelle par défaut utilisée actuellement par notre machine, plusieurs solutions sont possibles. La commande suivante retourne les routes locales, ainsi que la route par défaut, ce qui donne l’adresse IP de la passerelle.
ip route show
robert@vmi000000:~$ ip route show
default via 38.242.192.1 dev eth0 onlink
38.242.192.0/20 via 38.242.192.1 dev eth0
Dans mon cas, la passerelle est « 38.242.201.1 ».
Une ou plusieurs adresses de serveurs DNS auxquels le système ira demander les correspondances entre noms de domaine (www.webOdesign.net) et adresses IP (38.242.201.85).
Pour la trouver, il faut regarder le contenu du fichier « /etc/resolv.conf« 
robert@vmi000000:~$ sudo cat /etc/resolv.conf
Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8)
DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN
127.0.0.53 is the systemd-resolved stub resolver.
run "resolvectl status" to see details about the actual nameservers.
nameserver 161.97.189.51
nameserver 161.97.189.52
robert@vmi000000:~$ sudo cat /etc/resolv.conf
run "resolvectl status" see details about nameservers.
nameserver 161.97.189.51
nameserver 161.97.189.52