Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)

Comment fonctionnent réellement les barres de progression CLI (c’est juste une ligne réécrite)

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 :

  1. Revenir au début de la ligne
  2. 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 :

  • \x1b est le caractère d’échappement (ESC, ASCII 27)
  • [K est 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
  • \r ne 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.

Sortie désordonnée : barre de progression trop longue

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

Partager :

Articles Connexes

5 Commandes Essentielles pour Déboguer le Réseau sur Linux Minimal

5 Commandes Essentielles pour Déboguer le Réseau sur Linux Minimal

[Dernière mise à jour: 18 juin 2026]

Si vous êtes développeur et devez diagnostiquer des problèmes réseau dans des conteneurs ou sur Linux minimal, vous remarquerez que de nombreux outils …

Lire la suite about 5 Commandes Essentielles pour Déboguer le Réseau sur Linux Minimal
Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)

Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)

[Dernière mise à jour: 23 mai 2026]

Transformez un terminal gris et monotone en texte coloré et lisible qui parle vraiment votre langage. Vous êtes déjà resté plissé devant un mur de …

Lire la suite about Comment Faire Parler Votre Terminal en Couleur (avec les Codes ANSI)
*Ça marchait avant* : comment une mise à jour de l’OS a cassé mes sockets Rust

Ça marchait avant : comment une mise à jour de l’OS a cassé mes sockets Rust

Je n’ai changé aucune ligne de code. J’ai simplement mis à jour mon système d’exploitation (OS), et soudain mon outil Rust a cessé de fonctionner. …

Lire la suite about *Ça marchait avant* : comment une mise à jour de l’OS a cassé mes sockets Rust