Barre de progression et application console complète
Objectifs de la séance
- Afficher l'avancement d'un traitement long avec le widget
--gauge. - Structurer une application console complète autour d'une boucle de menu et d'une fonction par action.
- Rendre le script robuste : détection des outils disponibles, nettoyage systématique de l'écran, comportement correct en dehors d'un terminal interactif.
Notions théoriques
La barre de progression --gauge
Pour un traitement qui prend plusieurs secondes (une sauvegarde volumineuse, une installation), une simple --infobox ne suffit pas : l'utilisateur veut voir où en est le traitement. C'est le rôle de --gauge.
"$DIALOG" --gauge "texte" hauteur largeur [pourcentage_initial]
Particularité importante : --gauge ne lit pas ses arguments comme les autres widgets — elle lit son pourcentage sur son entrée standard, en continu, et ne se ferme que lorsque ce flux se termine. Elle s'alimente donc via un tube :
echo 50 | "$DIALOG" --gauge "Traitement en cours..." 8 50 0
Dans un cas réel, une boucle produit les valeurs successives :
for i in $(seq 0 10 100); do
echo "$i"
sleep 1
done | "$DIALOG" --gauge "Sauvegarde en cours..." 8 50 0
Faire évoluer aussi le texte affiché
En plus du pourcentage, on peut changer le message affiché en cours de route, grâce au protocole XXX :
XXX
40
Archivage de /var/www...
XXX
Un bloc XXX / nouveau pourcentage / nouveau message / XXX. On écrit généralement une petite fonction pour produire ce format :
avancement() {
echo "XXX"
echo "$1"
echo "$2"
echo "XXX"
}
Calculer le pourcentage
Le pourcentage se calcule à partir d'un compteur et d'un total, en réutilisant l'arithmétique entière vue en niveau 1 :
pourcentage=$(( compteur * 100 / total ))
Cette division est entière (pas de virgule) et un total égal à zéro provoque une division par zéro qui arrête le script. Vérifiez toujours que total n'est pas nul avant la boucle.
Tout ce que les commandes exécutées à l'intérieur de la boucle affichent (la sortie verbeuse de tar, un message d'erreur...) vient perturber le flux attendu par --gauge, qui n'attend que des nombres et des blocs XXX. Redirigez ce bruit vers /dev/null ou vers un fichier de log (rappel de la séance Bonnes pratiques du niveau 1).
dialog propose aussi --progressbox (affiche le contenu d'un flux au fur et à mesure, sans pourcentage) et --pause (compte à rebours) — deux widgets propres à dialog, absents de whiptail.
Une fonction par action
Rappel de la séance Fonctions du niveau 1 : local pour des variables qui ne fuient pas, return pour un code de retour, fonctions déclarées en haut du fichier, programme principal en bas.
Une application avec un menu à plusieurs entrées gagne à avoir une fonction par action : action_sauvegarder, action_restaurer, action_nettoyer, action_infos... Chaque fonction est autonome, testable, et le case du menu principal se contente de les appeler.
Il est également utile d'écrire des fonctions utilitaires transverses, appelées par toutes les actions, qui encapsulent une bonne fois pour toutes la formule de capture vue en séance Saisie utilisateur :
msg() {
"$DIALOG" --title "$1" --msgbox "$2" 10 60
}
confirmer() {
"$DIALOG" --yesno "$1" 8 50
}
demander() {
"$DIALOG" --inputbox "$1" 8 50 "${2:-}" 2>&1 1>/dev/tty
}
erreur() {
"$DIALOG" --title "Erreur" --msgbox "$1" 8 50
}
Avec ces fonctions, une action s'écrit ensuite en une ligne lisible : dossier=$(demander "Quel dossier ?" "/etc").
La boucle de menu principal
La structure vue en séance Menus et listes se complète maintenant avec de vraies fonctions :
while true; do
choix=$("$DIALOG" --menu "Menu principal" 15 50 4 \
"1" "Sauvegarder" \
"2" "Restaurer" \
"3" "Nettoyer les anciennes archives" \
"4" "Quitter" \
2>&1 1>/dev/tty) || break
case "$choix" in
1) action_sauvegarder ;;
2) action_restaurer ;;
3) action_nettoyer ;;
4) break ;;
esac
done
clear
Après chaque action, le script revient naturellement au menu. Un Cancel ou une touche Échap sur le menu lui-même (|| break) quitte la boucle.
Rendre le script robuste
Une application console destinée à être réellement utilisée mérite quelques précautions supplémentaires :
- Détection en cascade : tester
whiptailpuisdialog, avec un message d'erreur clair si ni l'un ni l'autre n'est disponible (vu en séance Première boîte de dialogue). - Mode non interactif : un script lancé par
cron(rappel du cours Bash niveau 2) n'a pas de terminal pour afficher des boîtes. Le test[ ! -t 0 ]détecte l'absence d'entrée interactive, et permet de basculer vers un déroulement silencieux enecho/journalisation plutôt que d'essayer d'ouvrir une interface qui ne s'affichera nulle part. - Nettoyage garanti :
trap 'clear' EXITassure que l'écran est remis dans un état normal même si le script s'interrompt de façon inattendue (rappel dutrapvu en Gestion d'erreurs avancée). - Personnalisation visuelle (optionnel) :
dialog --create-rc ~/.dialogrcgénère un fichier de thème modifiable ;whiptailse personnalise via la variable d'environnementNEWT_COLORS.
| Widget | Ce qu'il renvoie |
|---|---|
--msgbox | rien (juste un code de retour) |
--yesno | rien, seulement le code de retour (0 = Yes, 1 = No) |
--inputbox / --passwordbox | le texte saisi, sur stderr |
--menu | le tag choisi, sur stderr |
--checklist | les tags cochés, sur stderr |
--radiolist | le tag sélectionné, sur stderr |
--gauge | rien ; elle lit un pourcentage sur son entrée standard |
Exemple pratique
#!/bin/bash
# Script : admin_tui.sh
# Rôle : petite application console d'administration
# Auteur : Votre nom
set -eu
if command -v whiptail > /dev/null; then
DIALOG="whiptail"
elif command -v dialog > /dev/null; then
DIALOG="dialog"
else
echo "Ni dialog ni whiptail n'est installé." >&2
exit 1
fi
trap 'clear' EXIT
msg() {
"$DIALOG" --title "$1" --msgbox "$2" 10 60
}
confirmer() {
"$DIALOG" --yesno "$1" 8 50
}
action_sauvegarder() {
local dossiers=("/etc" "/var/www" "/home")
local total=${#dossiers[@]}
local compteur=0
(
for d in "${dossiers[@]}"; do
compteur=$((compteur + 1))
echo "$(( compteur * 100 / total ))"
tar czf "/tmp/sauvegarde_$(basename "$d").tar.gz" "$d" 2>/dev/null
sleep 1
done
) | "$DIALOG" --gauge "Sauvegarde en cours..." 8 50 0
msg "Sauvegarde" "Sauvegarde terminée pour ${#dossiers[@]} dossier(s)."
}
action_nettoyer() {
if confirmer "Supprimer les archives de plus de 30 jours ?"; then
find /tmp -name "sauvegarde_*.tar.gz" -mtime +30 -delete
msg "Nettoyage" "Anciennes archives supprimées."
fi
}
while true; do
choix=$("$DIALOG" --menu "Administration" 15 50 3 \
"1" "Sauvegarder" \
"2" "Nettoyer les anciennes archives" \
"3" "Quitter" \
2>&1 1>/dev/tty) || break
case "$choix" in
1) action_sauvegarder ;;
2) action_nettoyer ;;
3) break ;;
esac
done
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Nous construisons admin_tui.sh, la version finale et complète de notre outil.
Étape 1 — Créer les fonctions utilitaires msg() et confirmer()
Dès qu'un appel dialog/whiptail revient plusieurs fois avec les mêmes options, transformez-le en fonction utilitaire. Le reste du script devient plus court et plus lisible.
Étape 2 — Écrire la boucle de menu principal
Une boucle while true doit toujours avoir un moyen explicite d'en sortir (break) — ici via l'entrée "Quitter" et via le || break sur une annulation du menu.
Étape 3 — Alimenter la barre de progression depuis la boucle de sauvegarde
Redirigez systématiquement la sortie des commandes exécutées pendant une --gauge (ici 2>/dev/null sur tar) : le moindre message imprévu dans le flux perturbe l'affichage de la barre.
Étape 4 — Confirmer avant l'action destructive et nettoyer en sortie
Toute action irréversible (suppression, écrasement) doit passer par une --yesno, idéalement avec --defaultno. Le trap 'clear' EXIT garantit de son côté que l'écran redevient normal même si l'utilisateur interrompt le script en cours de route.
📌 Une solution
Exercices complémentaires
A. Consulter les journaux système
Ajoutez une entrée « Voir les logs » au menu principal, qui affiche les 100 dernières lignes de /var/log/syslog dans un --textbox (widget avec défilement).
B. Choisir le format de compression
Ajoutez un --radiolist proposant gzip, bzip2 ou xz, et appliquez réellement le choix à la commande tar (options -z, -j, -J).
C. Un mode non interactif
Ajoutez la gestion d'une option --auto (avec getopts, vu en Bash niveau 2) qui lance directement action_sauvegarder sans passer par le menu, utile pour un lancement automatique via cron. Combinez-la avec un test [ ! -t 0 ] pour basculer automatiquement en mode silencieux si le script est lancé sans terminal.
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
dialog / whiptail | Deux outils quasi interchangeables ; whiptail est presque toujours déjà présent sur Debian, dialog propose plus de widgets |
| Dimensions | hauteur largeur exprimées en lignes et colonnes de terminal, pas en pixels |
| Code de retour | 0 = OK/Yes, 1 = Cancel/No, 255 = Échap |
| Capture du résultat | Le résultat est écrit sur la sortie d'erreur → var=$("$DIALOG" ... 2>&1 1>/dev/tty) |
--msgbox / --yesno | Informer l'utilisateur / lui demander une confirmation |
--inputbox / --passwordbox | Saisir du texte libre / un mot de passe masqué |
--menu | Choix unique dans une liste ; renvoie le tag de l'entrée choisie |
--checklist / --radiolist | Sélection multiple / sélection exclusive, via des triplets tag item on|off |
--gauge | Barre de progression alimentée par un tube sur son entrée standard |
| Structure d'une application | Une fonction par action, une boucle while true + case pour le menu principal |
Aperçu de la prochaine séance
Ce sous-cours vous a appris à séparer clairement l'interface (les boîtes de dialogue) de la logique du script (les fonctions d'action) — un principe qui reste valable quel que soit l'outil utilisé. Si vous ne l'avez pas encore suivi en entier, le cours Bash niveau 2 approfondit justement plusieurs notions déjà croisées ici en filigrane : les tableaux, getopts, le traitement de texte et la planification avec cron. Vous pourrez ensuite retrouver les mêmes idées d'automatisation transposées à l'environnement Windows avec les cours PowerShell.