Comment écraser stdout en C

Dans la plupart des shells modernes, vous pouvez frapper les flèches haut et bas et il mettra, à l'invite, les commandes précédentes que vous avez exécutées. Ma question est, comment ça fonctionne?!

Il me semble que le shell manipule en quelque sorte stdout pour écraser ce qu'il a déjà écrit?

Je remarque que des programmes comme wget le font aussi. Quelqu'un at-il une idée de comment ils le font?

31
demandé sur radbrawler 2009-03-18 03:09:35

8 réponses

Il ne manipule pas stdout - il écrase les caractères qui ont déjà été affichés par le terminal.

Essayez ceci:

#include <stdio.h>
#include <unistd.h>
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, non? \r est un retour chariot, où le terminal interprète comme "replacer le curseur au début de la ligne actuelle".

Votre shell, s'il s'agit de bash, utilise la bibliothèque GNU readline , qui fournit des fonctionnalités beaucoup plus générales, y compris la détection des types de terminaux, l'historique gestion, touches programmables, etc.

Encore une chose-en cas de doute, la source de votre wget, votre shell, etc. sont tous disponibles.

41
répondu ephemient 2009-03-18 00:18:13

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) renverra 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 seule position, vous permettant d'écraser le dernier caractère, par exemple

#include <stdio.h>
#include <unistd.h>

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

for (i = 0; i < 100; i += 10) {
  printf("Processing: %3d%%\r",i); /* \r returns the caret to the line start */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);

printf("Processing: ");
for (i = 0; i < 100; i += 10) {
  printf("%c\b", progress[(i/10)%sizeof(progress)]); /* \b goes one back */
  fflush(stdout);
  sleep(1);
}
printf("\n"); /* goes to the next line */
fflush(stdout);

Utilisez fflush(stdout); Car la sortie standard est généralement tamponnée et les informations peuvent ne pas être immédiatement imprimées sur le sortie ou borne

22
répondu vladr 2014-09-30 12:51:28

En plus de \r et \ b, jetez un oeil à ncurses pour un contrôle avancé sur ce qui est sur l'écran de la console. (Y compris les colonnes, se déplacer arbitrairement, etc).

11
répondu SoapBox 2009-03-18 00:20:38

UN programme s'exécutant dans un terminal de texte / console peut manipuler le texte affiché dans sa console de différentes manières (rendre le texte en gras, déplacer le curseur, effacer l'écran, etc.). Ceci 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 Escape, ASCII 27).

Si stdout va 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 apparaissent dans le fichier (qui n'est généralement pas ce que vous voulez).

Il n'y a 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 codez pas directement les séquences d'échappement en dur dans votre logiciel (bien que vous le puissiez), mais utilisez une bibliothèque pour les imprimer, comme ncurses ou GNU readline mentionnés ci-dessus. Cela permet la compatibilité avec différents types de terminaux.

5
répondu sleske 2009-03-18 01:03:14

C'est fait avec le readline bibliothèque... Je ne suis pas sûr de savoir comment cela fonctionne dans les coulisses, mais je ne pense pas que cela ait quelque chose à voir avec stdout ou streams. Je soupçonne que readline utilise une sorte de commandes de terminal cryptiques (pour moi, au moins) - c'est-à-dire qu'il coopère avec le programme terminal qui affiche réellement votre session shell. Je ne sais pas que vous pouvez obtenir un comportement de type readline simplement en imprimant la sortie.

(pensez à ceci: stdout peut être redirigé vers un fichier, mais le haut/bas touches fléchées astuce ne fonctionne pas sur les fichiers.)

2
répondu David Z 2009-03-18 00:14:02

Vous pouvez utiliser le retour chariot pour simuler cela.

#include <stdio.h>

int main(int argc, char* argv[])
{
    while(1)
    {
        printf("***********");
        fflush(stdout);
        sleep(1);
        printf("\r");
        printf("...........");
        sleep(1);
    }

    return 0;
}
1
répondu Kknd 2009-03-18 00:17:50

Le programme le fait en imprimant des caractères spéciaux que le terminal interprète d'une manière spéciale. La version la plus simple de ceci est (sur la plupart des terminaux linux/unix) d'imprimer '\r' (retour chariot) à la sortie standard qui réinitialise la position du curseur sur le premier caractère de la ligne courante. Donc, la chose que vous écrivez ensuite écrasera la ligne que vous avez écrite précédemment. Cela peut être utilisé pour des indicateurs de progrès simples, par exemple.

int i = 0;
while (something) {
  i++;
  printf("\rprocessing line %i...", i);
  ...
}

Mais il y a évasion plus compliquée séquences de caractères qui sont interprétées de différentes manières. Toutes sortes de choses peuvent être faites avec cela, comme positionner le curseur à une position spécifique sur l'écran ou définir la couleur du texte. Si ou comment ces séquences de caractères sont interprétées dépend de votre terminal, mais une classe commune prise en charge par la plupart des terminaux sont séquences d'échappement ansi. Donc, si vous voulez du texte rouge, essayez:

printf("Text in \033[1;31mred\033[0m\n");
1
répondu sth 2009-03-18 04:07:46

Le moyen le plus simple est d'imprimer sur stdout le caractère de retour chariot ('\r').

Le curseur sera déplacé au début de la ligne, vous permettant d'écraser son contenu.

0
répondu morais 2009-03-18 00:21:16