How to delete and replace last line in the terminal using bash?

BashReplaceTerminalLine

Bash Problem Overview


I want to implement a progress bar showing elapsed seconds in bash. For this, I need to erase the last line shown on the screen (command "clear" erases all the screen, but I need to erase only the line of the progress bar and replace it with the new information).

Final result should look like:

$ Elapsed time 5 seconds

Then after 10 seconds i want to replace this sentence (in the same position in the screen) by:

$ Elapsed time 15 seconds

Bash Solutions


Solution 1 - Bash

The carriage return by itself only moves the cursor to the beginning of the line. That's OK if each new line of output is at least as long as the previous one, but if the new line is shorter, the previous line will not be completely overwritten, e.g.:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r0123456789"
0123456789klmnopqrstuvwxyz

To actually clear the line for the new text, you can add \033[K after the \r:

$ echo -e "abcdefghijklmnopqrstuvwxyz\r\033[K0123456789"
0123456789

http://en.wikipedia.org/wiki/ANSI_escape_code

Solution 2 - Bash

echo a carriage return with \r

seq 1 1000000 | while read i; do echo -en "\r$i"; done

from man echo:

-n     do not output the trailing newline
-e     enable interpretation of backslash escapes

\r     carriage return

Solution 3 - Bash

Derek Veit's answer works well as long as the line length never exceeds the terminal width. If this is not the case, the following code will prevent junk output:

before the line is written for the first time, do

tput sc

which saves the current cursor position. Now whenever you want to print your line, use

tput rc
tput ed
echo "your stuff here"

to first return to the saved cursor position, then clear the screen from cursor to bottom, and finally write the output.

Solution 4 - Bash

The \033 method didn't work for me. The \r method works but it doesn't actually erase anything, just puts the cursor at the beginning of the line. So if the new string is shorter than the old one you can see the leftover text at the end of the line. In the end tput was the best way to go. It has other uses besides the cursor stuff plus it comes pre-installed in many Linux & BSD distros so it should be available for most bash users.

#/bin/bash
tput sc # save cursor
printf "Something that I made up for this string"
sleep 1
tput rc;tput el # rc = restore cursor, el = erase to end of line
printf "Another message for testing"
sleep 1
tput rc;tput el
printf "Yet another one"
sleep 1
tput rc;tput el

Here's a little countdown script to play with:

#!/bin/bash
timeout () {
	tput sc
	time=$1; while [ $time -ge 0 ]; do
		tput rc; tput el
		printf "$2" $time
		((time--))
		sleep 1
	done
	tput rc; tput ed;
}

timeout 10 "Self-destructing in %s"

Solution 5 - Bash

In case the progress output is multi line, or the script would have already printed the new line character, you can jump lines up with something like:

printf "\033[5A"

which will make the cursor to jump 5 lines up. Then you can overwrite whatever you need.

If that wouldn't work you could try printf "\e[5A" or echo -e "\033[5A", which should have the same effect.

Basically, with escape sequences you can control almost everything in the screen.

Solution 6 - Bash

Use the carriage return character:

echo -e "Foo\rBar" # Will print "Bar"

Solution 7 - Bash

Can achieve it by placing carriage return \r.

In a single line of code with printf

for i in {10..1}; do printf "Counting down: $i\r" && sleep 1; done

or with echo -ne

for i in {10..1}; do echo -ne "Counting down: $i\r" && sleep 1; done

Solution 8 - Bash

If you just want to clear the previous line, the following might do the trick.

printf '\033[1A\033[K'

For multiple lines, use it in a loop:

for i in {1..10}; do
    printf '\033[1A\033[K'
done

This will clear the last 10 lines.

Solution 9 - Bash

Instead of using backslash in your echo's, you can use tput. To simplify this you can create a function:

#!/bin/bash

function consoleoneline {
    # Start process and save all outputs in a temporary directory
    tput sc # save current cursor in console
    local PIPE_DIRECTORY=$(mktemp -d)
    trap "rm -rf '$PIPE_DIRECTORY'" EXIT

    mkfifo "$PIPE_DIRECTORY/stdout"
    mkfifo "$PIPE_DIRECTORY/stderr"

    "$@" >"$PIPE_DIRECTORY/stdout" 2>"$PIPE_DIRECTORY/stderr" &
    local CHILD_PID=$!

    # Replace all outputs with a leading "›"
    # `tput` allows to reset the cursor to the previous line
    sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stdout" &
    sed "s/^/`tput rc;tput el`› /" "$PIPE_DIRECTORY/stderr" >&2 &

    # Wait command has exited, remove temporary directory and reset cursor
    wait "$CHILD_PID"
    rm -rf "$PIPE_DIRECTORY"
    tput ed
}

consoleoneline bash -c 'echo 1 && sleep 1 && echo 2 && sleep 1 && echo 3'

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDebuggerView Question on Stackoverflow
Solution 1 - BashDerek VeitView Answer on Stackoverflow
Solution 2 - BashKenView Answer on Stackoverflow
Solution 3 - BashUm.View Answer on Stackoverflow
Solution 4 - Bashlee8oiView Answer on Stackoverflow
Solution 5 - BashMr. GoferitoView Answer on Stackoverflow
Solution 6 - BashMikael SView Answer on Stackoverflow
Solution 7 - BashAkifView Answer on Stackoverflow
Solution 8 - BashDarkmanView Answer on Stackoverflow
Solution 9 - BashMatthias GünterView Answer on Stackoverflow