38

So, when wget gets a web page, it shows you a status bar that indicated how much the file(s) is/are downloaded. It looks like this:

25%[=============>______________________________________] 25,000 100.0K/s (underscores are spaces; I just couldn't figure out how to get more than one consecutive space in there)

However, instead of writing another line to stdout and adding another progress bar, it updates it, like this:

50%[===========================>________________________] 50,000 100.0K/s

And wget isn't the only example of this, either. For example, when you pipe something into less and then exit, your original prompt is still there, along with the result of whatever commands that you ran previously. It's like you never left.

So, my questions are, what is this called, how do I implement it, does it only work for a single line at a time, and can I use this in C?

2

3 Answers 3

44

First of all your question has nothing to do with bash but with the terminal. The terminal is responding for displaying the text of the programs and bash itself has no control over the programs once they launched.

Terminals offer control sequences to control color, font, cursor position and more. For a list of standardized terminal sequences have a look at http://www.termsys.demon.co.uk/vtansi.htm You can for example

  • position the cursor at the beginning of the line
  • delete the line afterwards
  • write a new line

to create a progress bar.

More advanced terminal escape sequences are typically terminal dependent, e.g. work only with Eterm or xterm. ncurses - is a programming library which to create interactive programs with the terminal so you won't have to use escape sequences.

How to overwrite an existing line with terminal sequences

echo long text
sleep 1
printf "\033[1A"  # move cursor one line up
printf "\033[K"   # delete till end of line
echo foo

How to overwrite an existing line without terminal sequence

One simple solution is to not write a newline at the end but write carriage return, which basically resets the cursor to the beginning of the line, e.g:

echo -n first 
sleep 1 
echo -ne "\rsecond"
echo

The \r or carriage return will put the cursor at the beginning of the line and allows you to overwrite the content of the line.

Switch between buffers like less or vi

The behavior of less is also due to a more advanced terminal feature, the alternate screen:

In VT102 mode, there are escape sequences to activate and deactivate an alternate screen buffer, which is the same size as the display area of the window. When activated, the current screen is saved and replaced with the alternate screen. Saving of lines scrolled off the top of the window is disabled until the normal screen is restored. The term‐ cap(5) entry for xterm allows the visual editor vi(1) to switch to the alternate screen for editing and to restore the screen on exit. A popup menu entry makes it simple to switch between the normal and alternate screens for cut and paste.

http://rosettacode.org/wiki/Terminal_control/Preserve_screen lists some example how to do it yourself, either via tput or via some escape sequences.

1
16

Instead of using echo which automatically appends a newline to the string, use printf "%s\r" whatever -- the carriage return sends the cursor to the beginning of the current line. example:

seq 1 15 | while read num; do printf "%2d\r" $num; sleep 1; done; echo ""
1
  • depending on your terminal's cursor, it may be more pleasing to do printf "\r%2d " $num Oct 10, 2018 at 20:07
0

I would also recommend those who found this topic to have a look at Bash Prompt HOWTO - Cursor Movement.

Examples:

- Position the Cursor:
  \033[<L>;<C>H
     Or
  \033[<L>;<C>f
  puts the cursor at line L and column C.
- Move the cursor up N lines:
  \033[<N>A
- Move the cursor down N lines:
  \033[<N>B
- Move the cursor forward N columns:
  \033[<N>C
- Move the cursor backward N columns:
  \033[<N>D

- Clear the screen, move to (0,0):
  \033[2J
- Erase to end of line:
  \033[K

- Save cursor position:
  \033[s
- Restore cursor position:
  \033[u

Some examples in C:

void saveCursorPosition() {
  printf("\033[s");
}

void restoreCursorPosition() {
  printf("\033[u");
}

void lineUP(short int times) {
  printf("\033[%iA", times);
}

void lineDown(short int times) {
  printf("\033[%iB", times);
}

Program Sample:

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

void saveCursorPosition() {
  printf("\033[s");
}

void restoreCursorPosition() {
  printf("\033[u");
}

void lineUP(short int times) {
  printf("\033[%iA", times);
}

void moveCursorBackwards(short int times) {
  printf("\033[%iD", times);
}

void printMainText() {

  printf("\n ╔═══════════════════════════════╗");
  printf("\n ║                               ║");
  printf("\n ║ Progress Bar                  ║");
  printf("\n ║                               ║");
  printf("\n ║ []                            ║");
  printf("\n ║                               ║");
  printf("\n ║ Press Ctrl+C to close         ║");
  printf("\n ║                               ║");
  printf("\n ╚═══════════════════════════════╝\n");

}

int main(int argc, char **argv) {

  printMainText();

  for (int progress=0; progress <= 10; progress++) {

    saveCursorPosition();
    lineUP(5);

    printf("\r ║     [");
    fflush(stdout);

    for (int i=0; i<progress; i++) {
      printf("=>]");
      fflush(stdout);
      moveCursorBackwards(2);
    }

    moveCursorBackwards(progress + 5);

    printf("%i%%", progress * 10);
    fflush(stdout);

    restoreCursorPosition();

    sleep(1);

  }

  return 0;

}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .