Cédric Temple

Administrateur système en logiciels libres

BASH, scripting avancé

2021-05-21 » notes pour plus tard

Ce contenu est fourni sous licence CC BY-NC-SA 4.0

Notes sur BASH

Itérateur

# faire un boucle de 1 à 9 :
for i in $(seq 0 9)
do
   ...
done

Tableaux

# déclarer un tableau
declare -a tableau
tableau=( "des pommes" "des poires")
# taille du tableau
echo "taille du tableau : ${#tableau[@]}"
# ajouter un élément au tableau :
tableau[2]="et des scoubidoubidou ouah"

# afficher un élément du tableau
echo "tableau : 0 : ${tableau[0]}"
echo "tableau : 1 : ${tableau[1]}"
echo "tableau : 2 : ${tableau[2]}"

# obtenir la liste des indices du tableau :
echo "les indices sont : ${!tableau[@]}"

# parcourir les éléments du tableau et les afficher :
for i in ${!tableau[@]}
do
echo "dans le tableau $i : ${tableau[$i]}"
done

Le résultat :

taille du tableau : 2
tableau : 0 : des pommes
tableau : 1 : des poires
tableau : 2 : et des scoubidoubidou ouah
les indices sont : 0 1 2
dans le tableau 0 : des pommes
dans le tableau 1 : des poires
dans le tableau 2 : et des scoubidoubidou ouah

Tableaux associatifs

# déclarer un tableau associatif
declare -A tableau_associatif
tableau_associatif=( [prenom]="Cedric" [nom]="Temple" [os]="GNU/Linux Debian" [taille]="1m83" )
# taille du tableau
echo "taille du tableau : ${#tableau_associatif[@]}"
# ajouter un élément au tableau :
tableau_associatif['age']="il a atteint un age à partir duquel c'est impoli de demander"
# taille du tableau
echo "nouvelle taille du tableau après ajout : ${#tableau_associatif[@]}"
# afficher un élément du tableau
echo "tableau : 'prenom' : ${tableau_associatif['prenom']}"
echo "tableau : 'nom' : ${tableau_associatif['nom']}"
echo "tableau : 'age' : ${tableau_associatif['age']}"

# obtenir la liste des indices du tableau :
echo "les indices sont : ${!tableau_associatif[@]}"

# parcourir les éléments du tableau et les afficher :
for i in ${!tableau_associatif[@]}
do
echo "dans le tableau $i : ${tableau_associatif[$i]}"
done

Le résultat :

taille du tableau : 4
nouvelle taille du tableau après ajout : 5
tableau : 'prenom' : Cedric
tableau : 'nom' : Temple
tableau : 'age' : il a atteint un age à partir duquel c'est impoli de demander
les indices sont : os age taille prenom nom
dans le tableau os : GNU/Linux Debian
dans le tableau age : il a atteint un age à partir duquel c'est impoli de demander
dans le tableau taille : 1m83
dans le tableau prenom : Cedric
dans le tableau nom : Temple

Tests sur les fichiers / dossiers

# vérifier qu'un élément existe
if [ -e ${FILE} ]
# vérifier qu'un élément existe et est un fichier (pas un dossier, ni un lien symbolique, ni ...)
if [ -f ${FILE} ]
# vérifier qu'un élément existe et est un répertoire / directory
if [ -d ${FILE} ]
# vérifier qu'un élément existe et est un lien symbolique
if [ -d ${FILE} ]
# vérifier qu'un élément existe et que sa taille est strictement positive
if [ -s ${FILE} ]
# vérifier qu'un élément existe et que cet élément a le droit de lecture positionné
if [ -r ${FILE} ]
# vérifier qu'un élément existe et que cet élément a le droit en écriture positionné
if [ -w ${FILE} ]
# vérifier qu'un élément existe et que cet élément a le droit d'exécution positionné
if [ -x ${FILE} ]

Faire des calculs / $i++

C’est toujours utile de pouvoir incrémenter une variable à l’aide de quelque chose comme $i++. En bash, ce n’est pas si simple.

let i=0
...
...
let i++

Pour faire un calcul :

let A=10
let B=2
let C=A+B

Fonctions

mafonction() {
    ls
    echo
    # ...
}
mafonction

mafonction_avec_parametres() {
    # on déclare les variables en local pour ne pas rentrer
    # en conflit avec les variables déclarées par ailleurs
    local monPremierParametre=$1
    local monDeuxiemeParametre=$2
    local monTroisiemeParametre=$3
    # ...
}

mafonction_avec_parametres valeur1 valeur2 valeur3

une_autre_fonction(){
    local nombreTotalParametre=$#
    # ...
    return 12
}
une_autre_fonction monParametre
retour=$?
# retour va avoir la valeur renvoyée par la fonction

Sortie en couleurs

Afficher du texte en couleurs permet d’améliorer la lecture des informations affichées à l’utilisateur. C’est grandement utile et ça facile la compréhension de l’utilisateur.

TEXT_GREEN="32"
TEXT_RED="31"
TEXT_BLUE="34"
TEXT_YELLOW="93"
TEXT_WHITE="37"
TEXT_BLACK="30"
TEXT_BOLD="1"
TEXT_UNDERLINE="4"
TEXT_BLINK="5"
TEXT_REVERSE="7" # inverse background and text color
TEXT_HIDDEN="8" # very useful for secrets/passwords
TEXT_RESET="0"

echo_with_colors(){
    local text=$1
    local color=$2
    local no_trailing_newline=0
    if [ $# -eq 3 ]
    then
        no_trailing_newline=1
    fi
    if [ ${no_trailing_newline} -eq 0 ]
    then
        echo -e "\e[${color}m${text}\e[${TEXT_RESET}m"
    else
        echo -n -e "\e[${color}m${text}\e[${TEXT_RESET}m"
    fi
}
echo_with_colors "TEXT in GREEN" "${TEXT_GREEN}"
echo_with_colors "TEXT in GREEN without trailing newline" "${TEXT_GREEN}" 1
echo_with_colors "TEXT in RED" "${TEXT_RED}"
echo_with_colors "TEXT in RED and BOLD" "${TEXT_RED};${TEXT_BOLD}"
echo_with_colors "BACKGROUND in RED" "${TEXT_RED};${TEXT_REVERSE}"

Debbuger un script

Permet de voir les commandes telles que lancées par le script :

#!/bin/bash -x

let A=10
let B=2
let C=A+B

Exemple de sortie :

+ let A=10
+ let B=2
+ let C=A+B
+ echo 12
12

Si on ne veut pas modifier le script pour ajouter l’option définitivement mais juste le faire de temps en temps :

bash -x monscript.sh

Si on veut debbuger une petite partie du script :

#!/bin/bash
...
...
set -x # le debug commence ici
...
...
set +x # le debug termine ici
...

Pour le mettre de façon plus explicite :

#!/bin/bash
...
set -o xtrace # le debug commence ici
...
set +o xtrace # le debug s'arrête ici

S’assurer que les variables sont remplies avant d’être utilisées

#!/bin/bash -u
# ou :
set -u
# ou :
set -o nounset

Exemple :

#!/bin/bash -xu

A=1
B=2
echo $A
echo $B
# on fait exprès d'utiliser une variable non initialisée pour générer une erreur
echo $C

Affichage :

+ A=1
+ B=2
+ echo 1
1
+ echo 2
2
./test.sh: ligne 8: C : variable sans liaison

Sortir dès qu’une commande ne retourne pas un code de retour nul

Petit rappel préalable : toute commande qui ne sort pas avec un code de retour nul indique qu’elle sort en erreur.

#!/bin/bash -e
# ou
set -e
# ou
set -o errexit

Exemple de script shell :

#!/bin/bash -xe

echo "A"
(exit 0)
echo "B"
(exit 1)
echo "C"

Le résultat de ce script montre que lorsque la commande en sous shell (celle dans des parenthèses) renvoie un code de retour nul, le shell continue alors que si le code de retour est non nul, le shell s’arrête (la dernière commande affichant C n’est pas exécutée) :

+ echo A
A
+ exit 0
+ echo B
B
+ exit 1

Code de retour en erreur transmis dans le pipe

set -o pipefail

Quelques explications. Par défaut, si une commande échoue dans un pipe, le traitement se poursuit. Exemple (bâteau) pour démontrer le problème :

#!/bin/bash

echo "erreur" | while read a; do if [ $a == "erreur" ]; then echo "ERREUR"; exit 127; fi; done | (read b ; echo "B : $b")
echo $?

Ce qui donne :

B : ERREUR
0

Ci-dessus, nous voyons que le traitement continue et que le code de retour final est 0. Parfois, on préfère arrêter le traitement et avoir comme code de retour final le code d’erreur.

Une option permet d’obtenir en résultat de tout le pipe, le code d’erreur (différent de 0 évidemment) le plus à droite du pipe :

set -o pipefail

Exemple quand on ajoute cette option :

#!/bin/bash

set -o pipefail
echo "erreur" | while read a; do if [ $a == "erreur" ]; then echo "ERREUR"; exit 127; fi; done | (read b ; echo "B : $b")
echo $?

Le résultat est :

B : ERREUR
127

Maintenant, si on veut que le traitement s’arrête : #!/bin/bash


set -o pipefail
set -o errexit
echo "erreur" | while read a; do if [ $a == "erreur" ]; then echo "ERREUR"; exit 127; fi; done | (read b ; echo "B : $b")
echo $?

Le résultat est :

B : ERREUR

À noter : le traitement dans le pipe se continue malgré les erreurs intermédiaires.

Les N premières ou dernières lignes

# les 20 premières lignes :
ls /usr/bin | head -n 20
# les 20 dernières lignes :
ls /usr/bin | tail -n 20

Tout SAUF les N premières ou les N dernières lignes

# ATTENTION !!! La syntaxe est (je trouve) particulière.
# A titre personnel, j'inverse toujours head/tail...
# ... et je ne me souviens jamais s'il faut mettre + ou -
# tout SAUF les 5 premières lignes : ajouter +1
# en fait, c'est "afficher tout à partir de la Nieme ligne" où N est ici la 6e ligne
ls /usr/bin | tail -n +6
# tout SAUF les 5 dernières lignes :
ls /usr/bin | head -n -5

Ce contenu est fourni sous licence CC BY-NC-SA 4.0