How to change the contents of a line on the terminal as opposed to writing a new one?

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?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

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 "33[1A"  # move cursor one line up
printf "33[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.

Method 2

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

seq 1 15 | while read num; do printf "%2dr" $num; sleep 1; done; echo ""

Method 3

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

Examples:

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

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

- Save cursor position:
  33[s
- Restore cursor position:
  33[u

Some examples in C:

void saveCursorPosition() {
  printf("33[s");
}

void restoreCursorPosition() {
  printf("33[u");
}

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

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

Program Sample:

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

void saveCursorPosition() {
  printf("33[s");
}

void restoreCursorPosition() {
  printf("33[u");
}

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

void moveCursorBackwards(short int times) {
  printf("33[%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;

}


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x