Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to overwrite stdout in C

In most modern shells, you can hit the up and down arrows and it will put, at the prompt, previous commands that you have executed. My question is, how does this work?!

It seems to me that the shell is somehow manipulating stdout to overwrite what it has already written?

I notice that programs like wget do this as well. Does anybody have any idea how they do it?

like image 607
Scott Avatar asked Mar 18 '09 00:03

Scott


2 Answers

It's not manipulating stdout -- it's overwriting the characters which have already been displayed by the terminal.

Try this:

#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;
}

That's pretty close to wget's output, right? \r is a carriage-return, which the terminal interprets as "move the cursor back to the start of the current line".

Your shell, if it's bash, uses the GNU Readline library, which provides much more general functionality, including detecting terminal types, history management, programmable key bindings, etc.

One more thing -- when in doubt, the source for your wget, your shell, etc. are all available.

like image 104
ephemient Avatar answered Nov 07 '22 04:11

ephemient


To overwrite the current standard output line (or parts of it) use \r (or \b.) The special character \r (carriage return) will return the caret to the beginning of the line, allowing you to overwrite it. The special character \b will bring the caret back one position only, allowing you to overwrite the last character, e.g.

#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);

Use fflush(stdout); because standard output is usually buffered and the information may not otherwise be immediately printed on the output or terminal

like image 25
vladr Avatar answered Nov 07 '22 02:11

vladr