
Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)
- 31 août 2026
- 8 mins de lecture
- Concepts de programmation , Qualité logicielle
Table des matières
Les barres de progression CLI ont l’air de se mettre à jour d’elles-mêmes.
[======== ] 40%
[############ ] 60%
[██████████ ] 50%
Mais ce n’est pas le cas.
Rien ne bouge réellement dans le terminal ; la même ligne est simplement réécrite encore et encore.
L’astuce qui se cache derrière cela est étonnamment simple : un seul caractère ramène le curseur au début de la ligne.
Vous pouvez le faire dans n’importe quel langage de programmation qui affiche des sorties dans un terminal, et vous n’avez besoin d’aucune bibliothèque spéciale.
L’astuce : revenir au début puis réécrire
Une barre de progression CLI fonctionne en réécrivant la même ligne à plusieurs reprises.
Chaque mise à jour fait deux choses :
- Revenir au début de la ligne
- Afficher le nouveau contenu pour écraser l’ancien contenu
Commencez par ramener le curseur au début de la ligne. C’est aussi simple que d’afficher un caractère de retour chariot (CR, ASCII numéro 13) :
1\r
Maintenant, affichez simplement le nouveau contenu pour écraser ce qui était là avant.
Notez que cela fonctionne bien lorsque le nouveau contenu a la même longueur que le précédent, ou une longueur supérieure. Nous couvrons le cas où le nouveau contenu est plus court que le précédent dans la section suivante sur les caractères restants.
Exemple simple : un indicateur de progression simple
Voici une petite démonstration côte à côte que vous pouvez repliquer dans n’importe quel langage :
La version « verbeuse » (une nouvelle ligne à chaque fois)
Commençons par ce qui ne fonctionne pas.
1n_iterations = 500
2for i in range(n_iterations):
3 # Rapport de progression
4 print(f"Progress: {i + 1} / {n_iterations}")
5
6 # ⟳ Simuler du travail...
7 import time
8 time.sleep(0.01)
Le résultat est lisible, mais il remplit inutilement le terminal avec des sorties.
Réécrire la même ligne (plus élégant)
Ajoutez maintenant \r (retour chariot) au début du texte affiché dans la ligne de progression.
Ajoutez aussi end="" pour éviter de créer de nouvelles lignes et garder le curseur sur la même ligne.
1n_iterations = 500
2for i in range(n_iterations):
3 # Rapport de progression
4 print(f"\rProgress: {i + 1} / {n_iterations}", end="")
5
6 # ⟳ Simuler du travail...
7 import time
8 time.sleep(0.005)
9print() # Passer à la ligne suivante après l’achèvement
La ligne de progression imprimée se met maintenant à jour sur place parce que chaque print :
- revient au début (
\r) - écrase ce qui était là avant
Cela fonctionne partout
Parce que le caractère \r vit à l’intérieur de votre texte, cette astuce fonctionne dans n’importe quel langage qui affiche des sorties dans un terminal.
Voici le même exemple dans quelques langages différents :
Node.js
1process.stdout.write(`\rProgress: ${i + 1} / ${nIterations}`);
Bash
1printf "\rProgress: %d / %d" "${i}" "${n_iterations}"
Go
1fmt.Printf("\rProgress: %d / %d", i, nIterations)
Rust
1print!("\rProgress: {} / {}", i, n_iterations);
Langage différent. Même astuce.
Ajouter de la couleur (maintenant, ça a l’air vivant)
Vous avez probablement remarqué que le texte de progression est assez banal. Corrigeons cela.
Si vous avez déjà vu comment colorer les sorties du terminal avec des codes ANSI, vous pouvez aller encore plus loin.
(Sinon, lisez : How to Make Your Terminal Talk in Color (with ANSI Codes))
1BCYAN = "\x1b[96m"
2SBLUE = "\x1b[38;5;67m"
3RESET = "\x1b[0m"
4
5n_iterations = 500
6for i in range(n_iterations):
7 # Rapport de progression
8 print(f"\rProgress: {BCYAN}{i + 1}{RESET} / {SBLUE}{n_iterations}{RESET}", end="")
9
10 # ⟳ Simuler du travail...
11 import time
12 time.sleep(0.005)
13print() # Passer à la ligne suivante après l’achèvement
Construire une vraie barre de progression
Transformons cela en quelque chose de plus visuel :
1# Définir la largeur de la barre de progression et le nombre total d’itérations
2total = 500
3bar_width = 30 # La barre de progression restera toujours exactement de cette largeur
4
5for i in range(total + 1):
6 # 1. Calculer le pourcentage de progression
7 ratio = i / total
8 percent = int(ratio * 100)
9
10 # 2. Mettre l’avancement total à l’échelle de la largeur fixe de la barre
11 filled_length = int(bar_width * ratio)
12
13 # 3. Construire les chaînes visuelles de la barre
14 filled = "█" * filled_length
15 empty = " " * (bar_width - filled_length)
16
17 # 4. Imprimer en utilisant '\r' pour écraser la ligne
18 print(f"\r[{filled}{empty}] {percent}%", end="", flush=True)
19
20 # ⟳ Simuler du travail (mis à l’échelle pour les totaux plus grands)
21 import time
22 time.sleep(0.005)
23
24print()
Vous obtenez maintenant :
Il ne se passe toujours rien de compliqué ici.
Il s’agit simplement d’une ligne qui est réécrite.
Comme dans l’exemple précédent, vous pouvez ajouter de la couleur à la barre de progression pour la rendre plus agréable visuellement.
Il suffit d’ajouter des codes ANSI à l’instruction print, comme suit.
1print(f"\r[{BCYAN}{filled}{RESET}{empty}] {SBLUE}{percent}%{RESET}", end="", flush=True)
Pourquoi cela casse parfois
C’est là que beaucoup de tutoriels s’arrêtent, et où les vrais problèmes commencent.
1. Caractères restants
Si le nouveau contenu est plus court que le précédent, des caractères restants resteront sur la ligne :
Remaining: 100%
Suivi de :
Remaining: 9%
Vous pourriez voir :
Remaining: 9%0%
🔧 Correctif :
Pour éviter cela, nous effaçons explicitement la ligne avant d’imprimer à nouveau.
C’est aussi fait en affichant quelque chose. Mais cette fois, nous utilisons une séquence spéciale de caractères que les terminaux comprennent : le code d’échappement ANSI.
Ce code d’échappement ANSI indique au terminal d’effacer tout ce qui se trouve du curseur jusqu’à la fin de la ligne :
1\x1b[K
Il est composé de deux parties :
\x1best le caractère d’échappement (ESC, ASCII 27)[Kest la commande permettant d’effacer depuis le curseur jusqu’à la fin de la ligne
La séquence combinée pour revenir au début et effacer la ligne est :
1\r\x1b[K
La progression mise à jour peut ensuite être affichée après cela, comme indiqué précédemment, et elle écrasera proprement le contenu précédent.
Tip
Si votre terminal est plus ancien et ne prend pas pleinement en charge ANSI, vous pouvez aussi effacer la ligne en affichant des espaces pour écraser le contenu précédent, comme ceci :
1print("\r" + " " * 50, end="") # Efface la ligne en affichant 50 espaces
2print("\rNew content", end="")
2. Retour à la ligne
Si votre ligne dépasse la largeur du terminal :
- elle passe à la ligne
\rne réinitialise que la ligne actuelle
Cela donne un résultat désordonné. Effacer la ligne avec \x1b[K ne suffira pas, car le curseur est sur la deuxième ligne.
🔧 Correctif :
- garder la ligne courte
- ou surveiller la taille du terminal et effacer les lignes concernées avant d’écrire. Cela nécessite un contrôle plus avancé du curseur. Faites-moi savoir si vous voulez que je traite ce sujet dans un prochain article.
Résumé
Une barre de progression CLI n’est pas un widget terminal sophistiqué. C’est simplement :
Une seule ligne de texte réécrite encore et encore, avec le curseur ramené au début à chaque fois.
Cette idée unique explique tout le schéma : utilisez \r pour réinitialiser, réécrire la même ligne, et effacer les caractères restants lorsque la prochaine mise à jour est plus courte. Les codes ANSI peuvent ensuite ajouter de la couleur et de la clarté, mais le mécanisme sous-jacent est toujours le même.
Vous pouvez utiliser une bibliothèque (par exemple, tqdm ou rich) uniquement lorsque votre sortie terminal dépasse une simple ligne de statut : tableaux de bord multi-lignes, tâches imbriquées, tableaux, spinners, ou mises en page terminal plus complexes. Pour une simple barre de progression, l’astuce intégrée suffit souvent.
En bref : construisez le comportement de base vous-même, et n’ajoutez une bibliothèque que lorsque votre interface dépasse une simple barre de progression sur une seule ligne.
Tip
Tout au long de cet article, j’ai utilisé \r pour désigner le caractère de retour chariot et \x1b pour le caractère d’échappement dans les exemples de codes ANSI.
C’est parce que ce sont les représentations les plus courantes et les plus largement prises en charge.
Cependant, nous pourrions également utiliser la représentation hexadécimale du caractère de retour chariot, qui est \x0d (car 13 en décimal est 0d en hexadécimal).
De plus, certains langages, comme JavaScript, prennent également en charge la forme Unicode (\u000d), et d’autres, comme C, prennent aussi en charge la forme octale (\015).
Bulletin d'information
Abonnez-vous à notre bulletin d'information et restez informé(e).


