29

In lots of Linux programs, like curl, wget, and anything with a progress meter, they have the bottom line constantly update, every certain amount of time. How do I do that in a bash script? All I can do now is just echo a new line, and that's not what I want because it builds up. I did come across something that mentioned "tput cup 0 0", but I tried it and it's kind of quirky. What's the best way?

3
  • I think this can be done with ncurses but maybe there's a better way - I'd be curious to find out too Commented May 2, 2011 at 19:20
  • 1
    After reading the answers, the gist of what I got was '\r' rewinds to the beginning of the line. so just echo without a newline [-n] and then `echo -ne '\r' will go back to the beginning of the line. Commented May 3, 2011 at 0:09
  • The accepted answer does not address the question. Commented Aug 12, 2023 at 10:48

6 Answers 6

46
{
  for pc in $(seq 1 100); do
    echo -ne "$pc%\033[0K\r"
    usleep 100000
  done
  echo
}

The "\033[0K" will delete to the end of the line - in case your progress line gets shorter at some point, although this may not be necessary for your purposes.

The "\r" will move the cursor to the beginning of the current line

The -n on echo will prevent the cursor advancing to the next line

Sign up to request clarification or add additional context in comments.

1 Comment

awesome! great little example
23

You can also use tput cuu1;tput el (or printf '\e[A\e[K') to move the cursor up one line and erase the line:

for i in {1..100};do echo $i;sleep 1;tput cuu1;tput el;done

1 Comment

That printf '\e[A\e[K' was the only solution that actually did the trick for me! Thanks for sharing it!
15

Small variation on linuts' code sample to move the cursor not to the beginning, but the end of the current line.

{
  for pc in {1..100}; do
    #echo -ne "$pc%\033[0K\r"
    echo -ne "\r\033[0K${pc}%"
    sleep 1
  done
  echo
}

Comments

9

printf '\r', usually. There's no reason for cursor addressing in this case.

Comments

8

To actually erase previous lines, not just the current line, you can use the following bash functions:

# Clears the entire current line regardless of terminal size.
# See the magic by running:
# { sleep 1; clear_this_line ; }&
clear_this_line(){
        printf '\r'
        cols="$(tput cols)"
        for i in $(seq "$cols"); do
                printf ' '
        done
        printf '\r'
}

# Erases the amount of lines specified.
# Usage: erase_lines [AMOUNT]
# See the magic by running:
# { sleep 1; erase_lines 2; }&
erase_lines(){
        # Default line count to 1.
        test -z "$1" && lines="1" || lines="$1"

        # This is what we use to move the cursor to previous lines.
        UP='\033[1A'

        # Exit if erase count is zero.
        [ "$lines" = 0 ] && return

        # Erase.
        if [ "$lines" = 1 ]; then
                clear_this_line
        else
                lines=$((lines-1))
                clear_this_line
                for i in $(seq "$lines"); do
                        printf "$UP"
                        clear_this_line
                done
        fi
}

Now, simply call erase_lines 5 for example to clear the last 5 lines in the terminal.

1 Comment

This is close(r), but ignores the possibility that there are fewer lines on the display than requested.
1

man terminfo(5) and look at the "cap-nam" column.

clr_eol=$(tput el)
while true
do
    printf "${clr_eol}your message here\r"
    sleep 60
done

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.