Compare commits
No commits in common. "e89330e284269a6883c0950879ec6657ee801f33" and "80eff1df92410aa43b49b81d9eae7bb98420bcfe" have entirely different histories.
e89330e284
...
80eff1df92
3 changed files with 5 additions and 402 deletions
Binary file not shown.
|
@ -1,398 +0,0 @@
|
||||||
Title: Bash avancé: Gérer les messages
|
|
||||||
Category: sysadmin
|
|
||||||
Tags: bash, script, pl-fr
|
|
||||||
Date: 2022-01-30 1:30
|
|
||||||
Cover: assets/backgrounds/article_bash_messages.jpg
|
|
||||||
|
|
||||||
Dans ce premier article de l'année 2022, nous allons voir comment gérer les
|
|
||||||
messages de sorties de nos scripts Bash. L'idée ici est de proposer trois type
|
|
||||||
de messages dans un fichiers que nous pourrons ensuite inclure dans nos scripts
|
|
||||||
à l'aide de la commande `source`.
|
|
||||||
|
|
||||||
Ces messages seront de 3 types différents:
|
|
||||||
|
|
||||||
* **messages standards** envoyés sur la sortie standard
|
|
||||||
* **messages de débogage** affichés si une variable `DEBUG` est positionnée et
|
|
||||||
envoyés vers la sortie d'erreur
|
|
||||||
* **message d'erreur** envoyés sur la sortie d'erreur.
|
|
||||||
|
|
||||||
Nous utiliserons quelques spécificité de bash nous permettant d'agrémenter nos
|
|
||||||
sorties.
|
|
||||||
|
|
||||||
## Une première version
|
|
||||||
|
|
||||||
Cette première ébauche de cette librairie contient 3 fonctions répondant à la
|
|
||||||
demande formulée en introduction. Appelons ce fichier `messages.sh`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/env bash
|
|
||||||
|
|
||||||
msg() {
|
|
||||||
local message="$*"
|
|
||||||
|
|
||||||
# Si la fonction est appelée sans paramètres, on la quitte
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
printf "%b\n" "$message"
|
|
||||||
}
|
|
||||||
|
|
||||||
debug() {
|
|
||||||
local message="$*"
|
|
||||||
# si la variable $DEBUG n'est pas définie ou si sa valeur
|
|
||||||
# est différente de 1, on quite notre fonction.
|
|
||||||
[ -z "$DEBUG" || $DEBUG -ne 1 ] && return
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
>&2 msg "DEBUG: $message"
|
|
||||||
}
|
|
||||||
|
|
||||||
error() {
|
|
||||||
local message="$*"
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
>&2 msg "ERROR: $message" }
|
|
||||||
```
|
|
||||||
|
|
||||||
Vous remarquez que les fonctions `error` et `debug` utilisent la fonction
|
|
||||||
`message` mais son appel est précédé de `>&2` afin que la sortie se fasse sur
|
|
||||||
`SRDERR`.
|
|
||||||
|
|
||||||
Le `printf` de notre fonction `msg` utilise `%b` pour afficher le contenu de la
|
|
||||||
`message`. Ainsi les séquences échappées par un antislash seront interprétées.
|
|
||||||
Nous aborderons le sujets dans la partie suivante.
|
|
||||||
|
|
||||||
Pour les tester, créons un script `test.sh` dans le même répertoire
|
|
||||||
que notre librairie avec le code suivant:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# inclusion de notre librairie de message, il ne faut pas oublier
|
|
||||||
# d'afficher une erreur et teminer notre script si il y a un problème
|
|
||||||
# lors de son chargement.
|
|
||||||
|
|
||||||
source message.sh || { >&2 printf "Can't load message.sh"; exit 1; }
|
|
||||||
|
|
||||||
debug "We will display a message"
|
|
||||||
msg "Test Message"
|
|
||||||
debug "We will display an error"
|
|
||||||
error "This is an error"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour tester il suffit de lancer la commande :
|
|
||||||
|
|
||||||
```shell
|
|
||||||
./test.sh
|
|
||||||
Test Message
|
|
||||||
ERROR: This is an error
|
|
||||||
```
|
|
||||||
|
|
||||||
Comme il n'y a pas de variable `$DEBUG` de définie, les message de débogage ne
|
|
||||||
sont pas affichés, pour tester ces messages il suffit de faire:
|
|
||||||
|
|
||||||
```none
|
|
||||||
$ DEBUG=1 ./test.sh
|
|
||||||
DEBUG: We will display a message
|
|
||||||
Test Message
|
|
||||||
DEBUG: We will display an error
|
|
||||||
ERROR: This is an error
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ajouter un peu de couleur
|
|
||||||
|
|
||||||
Personnellement, **j'aime avoir un peu de couleur dans mon terminal**, les
|
|
||||||
choses apparaissent souvent plus claire. Pour nos messages, nous pouvons faire
|
|
||||||
de même.
|
|
||||||
|
|
||||||
La commande `printf` permet d'insérer des code couleur (entre autres), utilisons
|
|
||||||
le rouge pour les messages d'erreur (logique non?) et le bleu pour les messages
|
|
||||||
de débogages
|
|
||||||
|
|
||||||
```bash
|
|
||||||
debug() {
|
|
||||||
local message="$*"
|
|
||||||
[[ -z $DEBUG || $DEBUG -ne 1 ]] && return
|
|
||||||
[ -n "$message" ] && >&2 msg "\e[34mDEBUG: $message\e[0m"
|
|
||||||
}
|
|
||||||
|
|
||||||
error() {
|
|
||||||
local message="$*"
|
|
||||||
[ -n message ] && >&2 msg "\e[31mERROR: $message\e[0m"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Ici la commande `\e[34m` permet de choisir la couleur bleue et `\e[0m` de
|
|
||||||
revenir à la normale. C'est ici que le choix de `%b` pour le formatage de la
|
|
||||||
variable `$message` est important: **`printf` interprètera nos commande
|
|
||||||
échappées avec l'antislash**.
|
|
||||||
|
|
||||||
La couleur c'est bien, mais si on décide de rediriger une sortie (ou les deux)
|
|
||||||
de notre script dans un fichier voici son contenu ouvert dans `vim`:
|
|
||||||
|
|
||||||
```none
|
|
||||||
[34mDEBUG: We will display a message[0m
|
|
||||||
Test Message
|
|
||||||
[34mDEBUG: We will display an error[0m
|
|
||||||
[31mERROR: This is an error[0m
|
|
||||||
```
|
|
||||||
|
|
||||||
Nous allons justement régler ce problème dans le paragraphe suivant.
|
|
||||||
|
|
||||||
## Sortie vers un fichier
|
|
||||||
|
|
||||||
Il est souvent nécessaire de **rediriger les sorties** d'un de nos script vers
|
|
||||||
un fichier. Surtout lorsqu'il est exécuté en dehors d'une session interactive --
|
|
||||||
dans une tâche *cron* par exemple.
|
|
||||||
|
|
||||||
Dans ce cas il peut être intéressant d'ajouter un horodatage en début de ligne
|
|
||||||
comme dans la plupart des applications qui effectuent de la journalisation. Bash
|
|
||||||
dispose d'un opérateur de test permettant de savoir si la sortie demandée est un
|
|
||||||
terminal interactif ou non: `-t`.
|
|
||||||
|
|
||||||
Le code prend un peu de poids :
|
|
||||||
|
|
||||||
* On ajoute une condition dans chacune de nos trois fonction d'origine afin de
|
|
||||||
tester le type de sortie.
|
|
||||||
* On y ajoute une fonction `log` qui se charge de traiter notre sortie
|
|
||||||
lorsque elle est redirigée vers un fichier en ajoutant une information de
|
|
||||||
date / heure au début de la ligne.
|
|
||||||
* Le format de cette date est paramétrable à l'aide de la variable `$DATE_FMT`,
|
|
||||||
dans l'exemple un *timestamp*.
|
|
||||||
|
|
||||||
Voici le nouveau code :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
DATE_FMT="+%s"
|
|
||||||
|
|
||||||
msg() {
|
|
||||||
local message="$*"
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
if [ -t 1 ]
|
|
||||||
then
|
|
||||||
printf "%b\n" "$message"
|
|
||||||
else
|
|
||||||
log "$message"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
log() {
|
|
||||||
local message="$*"
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
|
|
||||||
# On veux conserver les sauts de ligne et les tabulation du
|
|
||||||
# message, utilisons alors %b ...
|
|
||||||
printf "%s %b\n" "$(date $DATE_FMT)" "$message"
|
|
||||||
}
|
|
||||||
|
|
||||||
debug() {
|
|
||||||
local message="$*"
|
|
||||||
[[ -z $DEBUG || $DEBUG -ne 1 ]] && return
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
message="DEBUG: $message"
|
|
||||||
if [ -t 2 ]
|
|
||||||
then
|
|
||||||
>&2 msg "\e[34m$message\e[0m"
|
|
||||||
else
|
|
||||||
>&2 log "$message"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
error() {
|
|
||||||
local message="$*"
|
|
||||||
[ -z message ] && return
|
|
||||||
message="ERROR: $message"
|
|
||||||
if [ -t 2 ]
|
|
||||||
then
|
|
||||||
>&2 msg "\e[31m$message\e[0m"
|
|
||||||
else
|
|
||||||
>&2 log $message
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Améliorer les informations de débogage
|
|
||||||
|
|
||||||
Lors de la sortie d'information de débogage via notre fonction `debug`, il peut
|
|
||||||
être intéressant d'afficher des **informations supplémentaires** comme par
|
|
||||||
exemple la fonction en cours et le fichier source. Ces informations peuvent être
|
|
||||||
très précieuse surtout dans le cas d'un projet conséquent répartis sur plusieurs
|
|
||||||
fichiers sources.
|
|
||||||
|
|
||||||
Ces informations sont disponible via des variables spéciales de bash :
|
|
||||||
|
|
||||||
* `BASH_SOURCE`: un tableau reprenant **la pile des fichiers** scripts
|
|
||||||
utilisés. `$BASH_SOURCE[0]` représente le fichier source de la fonction
|
|
||||||
en cours.
|
|
||||||
* `FUNCNAME`: est un tableau reprenant la **liste des fonctions appelées**, en
|
|
||||||
quelque sorte notre pile d'appel. Cette variable est liée à la précédente,
|
|
||||||
`BASH_SOURCE[n]` représente la source de la fonction `FUNCNAME[n]` et
|
|
||||||
`FUNCNAME[0]` la fonction courante.
|
|
||||||
|
|
||||||
Dans notre fonction `debug`, `FUNCNAME[0]` correspond donc à `debug`, pour
|
|
||||||
retrouver la fonction appelante, il faut chercher du côté de `FUNCNAME[1]` (et
|
|
||||||
donc de `BASH_SOURCE[1]`).
|
|
||||||
|
|
||||||
Voici donc le nouveau code de notre fonction:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
debug() {
|
|
||||||
local message="$*"
|
|
||||||
[[ -z $DEBUG || $DEBUG -ne 1 ]] && return
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
|
|
||||||
# On affiche les informations supplémentaires pour le débogage
|
|
||||||
message="DEBUG [${BASH_SOURCE[1]}:${FUNCNAME[1]}]: $message"
|
|
||||||
if [ -t 2 ]
|
|
||||||
then
|
|
||||||
>&2 msg "\e[34m$message\e[0m"
|
|
||||||
else
|
|
||||||
>&2 log "$message"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Afin de tester le fonctionnement de notre modification, nous allons inclure un
|
|
||||||
autre fichier bash dans notre script de test. Voici notre fichier `include.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
myfunct() {
|
|
||||||
local a=10
|
|
||||||
debug "my a variable is $a"
|
|
||||||
msg "value of 'a' squared $(( a * a ))"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Et modifions notre fichier `test.sh` afin d'inclure notre fichier comme
|
|
||||||
ci-dessous :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
source message.sh || { >&2 printf "Can't load message.sh"; exit 1; }
|
|
||||||
source include.sh || { >&2 printf "Can't load include.sh"; exit 1; }
|
|
||||||
|
|
||||||
# Appel de notre fonction venue de include.sh
|
|
||||||
myfunct
|
|
||||||
|
|
||||||
debug "We will display a message"
|
|
||||||
msg "Test Message"
|
|
||||||
```
|
|
||||||
|
|
||||||
Et voici sa sortie:
|
|
||||||
|
|
||||||
```none
|
|
||||||
$ DEBUG=1 ./test.sh
|
|
||||||
DEBUG [include.sh:myfunct]: my a variable is 10
|
|
||||||
value of 'a' squared: 100
|
|
||||||
DEBUG [./test.sh:main]: We will display a message
|
|
||||||
Test Message
|
|
||||||
```
|
|
||||||
|
|
||||||
Le fichier source et la fonction appelée sont bien affichés. Bash dispose
|
|
||||||
d'autre variables utiles que vous trouverez dans l'aide : `man bash`.
|
|
||||||
|
|
||||||
## Améliorer les sorties d'erreur
|
|
||||||
|
|
||||||
Avec ce que nous venons de voir, nous pouvons maintenant améliorer la sortie
|
|
||||||
d'erreur. Pourquoi nous n'afficherions pas, lorsque le mode de débogage est
|
|
||||||
activé, une sorte de *stack trace*?
|
|
||||||
|
|
||||||
Pour ce faire, nous pouvons utiliser la variable `${#FUNCNAME[@]}` qui va nous
|
|
||||||
donner le nombre de fonctions appelées. Voici le code de notre nouvelle fonction
|
|
||||||
`error`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
error() {
|
|
||||||
local message="$*"
|
|
||||||
[ -z "$message" ] && return
|
|
||||||
message="ERROR: $message"
|
|
||||||
|
|
||||||
# Nous affichons notre "stack trace si le mode débogage est activé
|
|
||||||
if [[ -n $DEBUG && $DEBUG -eq 1 ]]
|
|
||||||
then
|
|
||||||
message="$message\n\tstack trace:\n"
|
|
||||||
|
|
||||||
# Il nous suffit pour ça de parcourir notre tableau FUNCNAME et
|
|
||||||
# d'afficher le BASH_SOURCE et BASH_LINENO correspondant
|
|
||||||
for (( i=1; i<${#FUNCNAME[@]}; i++ ))
|
|
||||||
do
|
|
||||||
message="${message}\t source:${BASH_SOURCE[i]}"
|
|
||||||
message="${message} function:${FUNCNAME[$i]}"
|
|
||||||
|
|
||||||
# Attention, il faut prendre ici la valeur de n-1 pour BASH_LINENO
|
|
||||||
message="${message} line:${BASH_LINENO[$i-1]}\n"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
if [ -t 2 ]
|
|
||||||
then
|
|
||||||
>&2 msg "\e[31m$message\e[0m"
|
|
||||||
else
|
|
||||||
>&2 log "$message"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Pour tester notre nouvelle fonction, rajoutons le code suivant dans le fichier
|
|
||||||
`include.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
|
|
||||||
check_file() {
|
|
||||||
if [ ! -f "monfichier.txt" ]
|
|
||||||
then
|
|
||||||
display_error "File monfichier.txt not found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
display_error() {
|
|
||||||
error "$*"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Et enfin modifions notre fichier `test.sh` comme ci-dessous:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
source message.sh || { >&2 printf "Can't load message.sh"; exit 1; }
|
|
||||||
source include.sh || { >&2 printf "Can't load include.sh"; exit 1; }
|
|
||||||
myfunct
|
|
||||||
check_file
|
|
||||||
debug "We will display a message"
|
|
||||||
msg "Test Message"
|
|
||||||
error "This is a simple error message"
|
|
||||||
exit 0
|
|
||||||
```
|
|
||||||
|
|
||||||
Lors de l'exécution de notre script de test avec le mode débogage, nous pouvons
|
|
||||||
voir que tous les éléments demandés sont présent:
|
|
||||||
|
|
||||||
```none
|
|
||||||
$ DEBUG=1 ./test.sh
|
|
||||||
DEBUG [include.sh:myfunct]: my a variable is 10
|
|
||||||
value of 100
|
|
||||||
ERROR: File monfichier.txt not found
|
|
||||||
stack trace:
|
|
||||||
source:include.sh function:display_error line:18
|
|
||||||
source:include.sh function:check_file line:13
|
|
||||||
source:./test.sh function:main line:6
|
|
||||||
|
|
||||||
DEBUG [./test.sh:main]: We will display a message
|
|
||||||
Test Message
|
|
||||||
ERROR: This is a simple error message
|
|
||||||
stack trace:
|
|
||||||
source:./test.sh function:main line:9
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
## En conclusion
|
|
||||||
|
|
||||||
Nous avons vu tout au long de cet article comment utiliser des fonctions pour
|
|
||||||
afficher vos messages, que se soit pour déboguer, afficher des erreurs ou de
|
|
||||||
simples messages.
|
|
||||||
|
|
||||||
Pensez que vous pouvez placer votre "bibliothèque" dans un endroit pr esent dans
|
|
||||||
la variable d'environnement `$PATH` et ainsi l'inclure dans n'importe quel
|
|
||||||
script.
|
|
||||||
|
|
||||||
Les fichiers d'exemple sont disponibles [ici]({attach}files/scripts.tar.gz).
|
|
|
@ -3,8 +3,9 @@ slug: a-propos
|
||||||
cover: assets/backgrounds/gnu.jpg
|
cover: assets/backgrounds/gnu.jpg
|
||||||
|
|
||||||
Je suis Yorick, marié, papa de deux enfants, habitant Bordeaux et
|
Je suis Yorick, marié, papa de deux enfants, habitant Bordeaux et
|
||||||
**administrateur système spécialisé en technologies Open-source**.Je suis aussi
|
**administrateur système spécialisé en technologies Open-source**. Je suis
|
||||||
étudiant en Master Ingénieurie de l'Informatique à l'Université de Bordeaux.
|
actuellement administrateur système. Je suis aussi étudiant en licence
|
||||||
|
professionnelle ADSILLH.
|
||||||
|
|
||||||
Si vous voulez en apprendre plus, [téléchargez mon CV][l_cv], ou son
|
Si vous voulez en apprendre plus, [téléchargez mon CV][l_cv], ou son
|
||||||
[code source][l_cv_code].
|
[code source][l_cv_code].
|
||||||
|
@ -14,8 +15,8 @@ Si vous voulez en apprendre plus, [téléchargez mon CV][l_cv], ou son
|
||||||
### le collectif Giroll
|
### le collectif Giroll
|
||||||
|
|
||||||
[Giroll][l_giroll], pour Gironde Logiciels Libres, et un collectif héthéroclite
|
[Giroll][l_giroll], pour Gironde Logiciels Libres, et un collectif héthéroclite
|
||||||
réuni autour des cultures libres, je suis un **membre fondateur** et ancien
|
réuni autour des cultures libres, je suis un **membre fondateur** et animateur de
|
||||||
animateur de l'émission de radio mensuelle "Radio Giroll".
|
l'émission de radio mensuelle "Radio Giroll".
|
||||||
|
|
||||||
### Khal
|
### Khal
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue