tags:

views:

2425

answers:

8

Not sure if this is phrased well or not...

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're doing it?

Thanks!

EDIT: Thanks to everybody, this has been a huuuge help. I appreciate it!

+2  A: 

It's done with the readline library... I'm not sure how it works behind the scenes but I don't think it has anything to do with stdout or streams. I suspect readline uses some sort of cryptic (to me, at least) terminal commands - that is, it cooperates with the terminal program that actually displays your shell session. I don't know that you can get readline-like behavior just by printing output.

(Think about this: stdout can be redirected to a file, but the up/down-arrow keys trick doesn't work on files.)

David Zaslavsky
+9  A: 

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>

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

Cheers, V.

vladr
Might want to add an #include <unistd.h> for the sleep function.
Prairiedogg
A: 

You can use carriage return to simulate this.

#include <stdio.h>

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

    return 0;
}
Kknd
+6  A: 

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.

ephemient
+9  A: 

In addition to \r and \b, take a look at ncurses for some advanced control over what's on the console screen. (Including columns, moving around arbitrarily, etc).

SoapBox
A: 

The simplest way is to print to stdout the carriage return character ('\r').

The cursor will be moved to the start of the line, allowing you to overwrite its contents.

morais
+1  A: 

The program does this by printing special characters that the terminal interprets in a special way. The most simple version of this is (on most linux/unix terminals) to print '\r' (carriage return) to the normal stdout which resets the cursor position to the first character in the current line. So the thing you write next will overwrite the line you wrote previously. This can be used for simple progress indicators, for example.

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

But there are more complicated escape characters sequences that are interpreted in various ways. All kinds of things can be done with this, like positioning the cursor at a specific position on the screen or setting the text color. If or how these character sequences are interpreted depends on your terminal, but a common class supported by most terminals are ansi escape sequences. So if you want red text, try:

printf("Text in \033[1;31mred\033[0m\n");
sth
It's not the shell that processes these special characters, it's the terminal the shell is running in, i.e. an instance of xterm, rxvt, konsole or similar.
sleske
you of course are right.
sth
+2  A: 

A program running in a text terminal / console can manipulate the text displayed in its console in various ways (make text bold, move cursor, clear screen etc.). This is accomplished by printing special character sequences, called "escape sequences" (because they usually start with Escape, ASCII 27).

If stdout goes to a terminal which understands these escape sequences, the display of the terminal will change accordingly.

If you redirect stdout to a file, the escape sequences will appear in the file (which is usually not what you want).

There is no complete standard for escape sequences, but most terminals use the sequences introduced by VT100, with many extensions. This is what most terminals under Unix/Linux (xterm, rxvt, konsole) and others like PuTTY understand.

In practice, you would not directly hardcode escape sequences into your software (though you could), but use a library to print them, such as ncurses or GNU readline mentioned above. This allows compatibility with different terminal types.

sleske