27 votes

Comment écraser stdout en C

Dans la plupart des shells modernes, vous pouvez appuyer sur les flèches haut et bas et cela vous affichera, dans l'invite de commande, les commandes précédentes que vous avez exécutées. Ma question est, comment cela fonctionne ? !

Il me semble que le shell manipule d'une manière ou d'une autre stdout pour écraser ce qu'il a déjà écrit ?

Je remarque que des programmes comme wget font également cela. Est-ce que quelqu'un a une idée de comment ils font cela ?

36voto

ephemient Points 87003

Il ne s'agit pas de manipuler stdout -- il s'agit de remplacer les caractères qui ont déjà été affichés par le terminal.

Essayer ceci:

#include 
#include 
static char bar[] = "======================================="
                    "======================================>";
int main() {
    int i;
    for (i = 77; i >= 0; i--) {
        printf("[%s]\r", &bar[i]);
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return 0;
}

C'est assez proche de la sortie de wget, n'est-ce pas? \r est un retour-chariot, que le terminal interprète comme "déplacer le curseur de retour au début de la ligne actuelle".

Votre shell, s'il s'agit de bash, utilise la bibliothèque GNU Readline, qui offre une fonctionnalité beaucoup plus générale, y compris la détection des types de terminal, la gestion de l'historique, les liaisons de touches programmables, etc.

Une dernière chose -- en cas de doute, la source de votre wget, votre shell, etc. sont toutes disponibles.

16voto

vladr Points 34562

Pour remplacer la ligne de sortie standard actuelle (ou des parties de celle-ci) utilisez \r (ou \b.) Le caractère spécial \r (retour chariot) ramènera le curseur au début de la ligne, vous permettant de l'écraser. Le caractère spécial \b ramènera le curseur d'une position seulement, vous permettant d'écraser le dernier caractère, par exemple.

#include 
#include 

int i;
const char progress[] = "|/-\\";

for (i = 0; i < 100; i += 10) {
  printf("Traitement en cours : %3d%%\r",i); /* \r ramène le curseur au début de la ligne */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* passe à la ligne suivante */
fflush(stdout);

printf("Traitement en cours : ");
for (i = 0; i < 100; i += 10) {
  printf("%c\b", progress[(i/10)%sizeof(progress)]); /* \b recule d'une position */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* passe à la ligne suivante */
fflush(stdout);

Utilisez fflush(stdout); car la sortie standard est généralement mise en mémoire tampon et les informations pourraient ne pas être immédiatement imprimées sur la sortie ou le terminal.

11voto

SoapBox Points 14183

En plus de \r et \b, jetez un coup d'œil à ncurses pour un contrôle avancé sur ce qui s'affiche à l'écran de la console. (Y compris les colonnes, les déplacements arbitraires, etc).

4voto

sleske Points 29978

Un programme s'exécutant dans un terminal texte / console peut manipuler le texte affiché dans sa console de diverses manières (mettre du texte en gras, déplacer le curseur, effacer l'écran, etc.). Cela est accompli en imprimant des séquences de caractères spéciales, appelées "séquences d'échappement" (car elles commencent généralement par Echappement, ASCII 27).

Si stdout est redirigé vers un terminal qui comprend ces séquences d'échappement, l'affichage du terminal changera en conséquence.

Si vous redirigez stdout vers un fichier, les séquences d'échappement apparaîtront dans le fichier (ce qui est généralement ce que vous ne voulez pas).

Il n'existe pas de norme complète pour les séquences d'échappement, mais la plupart des terminaux utilisent les séquences introduites par VT100, avec de nombreuses extensions. C'est ce que la plupart des terminaux sous Unix/Linux (xterm, rxvt, konsole) et d'autres comme PuTTY comprennent.

En pratique, vous ne coderez pas directement des séquences d'échappement dans votre logiciel (bien que vous puissiez le faire), mais utiliserez une bibliothèque pour les imprimer, telle que ncurses ou GNU readline mentionnée ci-dessus. Cela permet une compatibilité avec différents types de terminaux.

2voto

David Z Points 49476

C'est fait avec la bibliothèque readline ... Je ne suis pas sûr de comment cela fonctionne en coulisse mais je ne pense pas que cela ait quoi que ce soit à voir avec stdout ou les streams. Je pense que readline utilise une sorte de commandes cryptiques (du moins pour moi) de terminal - c'est-à-dire, il coopère avec le programme de terminal qui affiche réellement votre session shell. Je ne crois pas que vous puissiez obtenir un comportement similaire à readline juste en imprimant la sortie.

(Réfléchissez à ceci : stdout peut être redirigé vers un fichier, mais le truc des touches de flèche haut/bas ne fonctionne pas sur les fichiers.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X