Aller au contenu principal

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 ))
attention

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.

attention

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).

info

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 whiptail puis dialog, 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 en echo/journalisation plutôt que d'essayer d'ouvrir une interface qui ne s'affichera nulle part.
  • Nettoyage garanti : trap 'clear' EXIT assure que l'écran est remis dans un état normal même si le script s'interrompt de façon inattendue (rappel du trap vu en Gestion d'erreurs avancée).
  • Personnalisation visuelle (optionnel) : dialog --create-rc ~/.dialogrc génère un fichier de thème modifiable ; whiptail se personnalise via la variable d'environnement NEWT_COLORS.
WidgetCe qu'il renvoie
--msgboxrien (juste un code de retour)
--yesnorien, seulement le code de retour (0 = Yes, 1 = No)
--inputbox / --passwordboxle texte saisi, sur stderr
--menule tag choisi, sur stderr
--checklistles tags cochés, sur stderr
--radiolistle tag sélectionné, sur stderr
--gaugerien ; 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


Comment le widget --gauge reçoit-il la progression à afficher ?


Que se passe-t-il si le flux qui alimente --gauge ne se termine jamais ?


À quoi sert le bloc XXX envoyé au flux d'une --gauge ?


Pourquoi regrouper la formule 2>&1 1>/dev/tty dans une fonction demander() ?


Comment un script détecte-t-il qu'il est lancé sans terminal interactif (par exemple par cron) ?


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()


Bonne pratique - Encapsuler les formules répétitives

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


Bonne pratique - Toujours prévoir une sortie

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


Bonne pratique - Isoler le bruit des commandes

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


Bonne pratique - Une confirmation explicite avant toute suppression

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

NotionRésumé
dialog / whiptailDeux outils quasi interchangeables ; whiptail est presque toujours déjà présent sur Debian, dialog propose plus de widgets
Dimensionshauteur largeur exprimées en lignes et colonnes de terminal, pas en pixels
Code de retour0 = OK/Yes, 1 = Cancel/No, 255 = Échap
Capture du résultatLe résultat est écrit sur la sortie d'erreur → var=$("$DIALOG" ... 2>&1 1>/dev/tty)
--msgbox / --yesnoInformer l'utilisateur / lui demander une confirmation
--inputbox / --passwordboxSaisir du texte libre / un mot de passe masqué
--menuChoix unique dans une liste ; renvoie le tag de l'entrée choisie
--checklist / --radiolistSélection multiple / sélection exclusive, via des triplets tag item on|off
--gaugeBarre de progression alimentée par un tube sur son entrée standard
Structure d'une applicationUne 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.