Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash - Clearing the last output correctly

Tags:

bash

tput

I'm trying to create an update-able progress status. In order to do that, I need to be able to clear the last output in its entirety so that I can update it. Carriage returns can work, but when the output is longer than the terminal width and wraps around, it will fail to clear the last output. So I'm using tput:

n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    tput ed #clear
    tput sc #save cursor
    echo -n "Progress: ${n}%"
    tput rc #restore cursor
    sleep 1s
done
echo

But this will fail if the output is long enough that it forces the terminal to scroll up. When that happens, the saved cursor position is no longer correct and it will fail to clear the last output correctly.

For example, if the cursor is currently at the bottom of the terminal and the output is longer than the terminal width, it will force the terminal to scroll up, invalidating the previously saved cursor position.

So are there any ways to ensure that the cursor will never the end of the terminal in Bash? Or maybe some other alternative methods to prevent this problem?

EDIT: I made my own version based on F. Hauri's answer, simplified for my use case

#!/bin/bash
str=$(head -c 338 < /dev/zero | tr '\0' '\141')
len="${#str}"
col=$(tput cols)
lines=$(( ((len + col - 1) / col) - 1 ))

echo -ne "${str}\r"
(( len > col )) && tput cuu "$lines"

sleep 3s

tput ed
like image 576
what the Avatar asked May 27 '20 08:05

what the


People also ask

How do I clear previous output in terminal?

You can use Ctrl+L keyboard shortcut in Linux to clear the screen. It works in most terminal emulators.

How do I clear the console in bash?

When using the bash shell, you can also clear the screen by pressing Ctrl + L .

Can I delete .bash history?

Clear all bash history by using history command: 'date' command will display the current date and time. 'ls' command will display the list of the files and folders of the current location. 'clear' command will clear the terminal screen. Run the history command to display the current bash history.

How do I undo the last bash command?

In Bash and Zsh ctrl + w erases backwards from where the cursor is.

How to recompute the output of the last command in Bash?

You can use $ (!!) to recompute (not re-use) the output of the last command. The !! on its own executes the last command. The answer is no. Bash doesn't allocate any output to any parameter or any block on its memory. Also, you are only allowed to access Bash by its allowed interface operations.

How to clear Bash history in Linux?

How the bash history can be cleared by the mentioned options are shown in this article. Run the following command to create some bash history. ‘date’ command will display the current date and time. ‘ls’ command will display the list of the files and folders of the current location. ‘ clear ‘ command will clear the terminal screen.

How to find the exit code of the previous command in Bash?

In the case of bash, the exit code of the previous command is accessible using the shell variable “$?”. Launch a terminal, and run any command. Check the value of the shell variable “$?” for the exit code. $ echo $? As the “date” command ran successfully, the exit code is 0.

How to check if the last command has been executed properly?

The most obvious one is, of course, to verify whether the last command is executed properly, especially if the command doesn’t generate any output. In the case of bash, the exit code of the previous command is accessible using the shell variable “$?”. Launch a terminal, and run any command.


1 Answers

Something tricky

Inspired by How to get the cursor position in bash?

#!/bin/bash

lineformat="This is a very long line with a lot of stuff so they will take " 
lineformat+="more than standard terminal width (80) columns... Progress %3d%%" 

n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    printf -v outputstring "$lineformat" $n
    twidth=$(tput cols)      # Get terminal width
    theight=$(tput lines)    # Get terminal height
    oldstty=$(stty -g)       # Save stty settings
    stty raw -echo min 0     # Suppres echo on terminal
    # echo -en "\E[6n"
    tput u7                  # Inquire for cursor position
    read -sdR CURPOS         # Read cursor position
    stty $oldstty            # Restore stty settings
    IFS=\; read cv ch <<<"${CURPOS#$'\e['}" # split $CURPOS
    uplines=$(((${#outputstring}/twidth)+cv-theight))
    ((uplines>0)) &&
        tput cuu $uplines    # cursor up one or more lines
    tput ed                  # clear to end of screen
    tput sc                  # save cursor position
    echo -n "$outputstring"
    tput rc                  # restore cursor
    sleep .0331s
done
echo

As tput cols and tput lines is initiated at each loop, you could resize window while running, cuu argument will be re-computed.

More complex sample

  • Using trap WINCH for querying terminal size only when window is resized
  • Addind newlines for scrolling up before cuu
  • Reducing forks to tput

There:

#!/bin/bash

lineformat="This is a very long line with a lot of stuff so they will take " 
lineformat+="more than standard terminal width (80) columns... Progress %3d%%" 

getWinSize() {
    {
        read twidth
        read theight
    } < <(
        tput -S - <<<$'cols\nlines'
    )
}
trap getWinSize WINCH
getWinSize

getCpos=$(tput u7)
getCurPos() {
    stty raw -echo min 0
    echo -en "$getCpos"
    read -sdR CURPOS
    stty $oldstty
    IFS=\; read curv curh <<<"${CURPOS#$'\e['}"
}
oldstty=$(stty -g)

before=$(tput -S - <<<$'ed\nsc')
after=$(tput rc)
n=0
while [[ $n -ne 100 ]]; do
    n=$((n+1))
    printf -v outputstring "$lineformat" $n
    getCurPos
    uplines=$(((${#outputstring}/twidth)+curv-theight))
    if ((uplines>0)) ;then
        printf -v movedown "%${uplines}s" ''
        echo -en "${movedown// /\\n}"
        tput cuu $uplines
    fi
    printf "%s%s%s" "$before" "$outputstring" "$after"
    sleep .05
done

downlines=$((${#outputstring}/twidth))
printf -v movedown "%${downlines}s" ''
echo "${movedown// /$'\n'}"
like image 121
F. Hauri Avatar answered Oct 17 '22 23:10

F. Hauri