2

I have made a pretty basic progress bar in BASH like this :-

doned=$1  #amount completed
total=$2  #total amount


doned=`echo $doned $total | awk '{print ($1/$2)}'`
total=`tput cols | awk '{print $1-10}'`
doned=`echo $doned $total | awk '{print int(($1*$2))}'`


echo -n $doned"% [ "

for i in $(seq 1 $doned); do
    echo -n "="
done

for i in $(seq $((doned+1)) $total); do
    echo -n "-"
done

echo " ]"

This works exactly like I want it to.

This script runs within a loop in another script. I want it to always display at the bottom of the terminal, or any other fixed place.

The loop is somewhat like this:-

for i in 10 20 30 40; do
    echo -n "Do you want to continue on to the next step? (y/n): ";
    read $yn
    if [[ "$yn" == "n" ]] || [[ "$yn" == "N" ]]; then
        exit 1; # stop the script
        d_3=0;
    fi
    doned=`some command` ### Get number of completed files
    totalss=`some other command` ### Get total number of files
    bash ./libraries/prog.sh $doned $totalss
done

So what I want is that the progress bar remain at the bottom even when inputting values, or displaying something. Is there a way to do this, preferably without having to install anything extra? Most of the computers I want to use this script on are either Debian version 8+ or Ubuntu 16+ systems

  • 1
    Have you considered using things like pv, or dialog/whiptail --gauge? – Stéphane Chazelas Sep 12 '18 at 16:03
  • No, I hadn't. I wanted opinions on what would be the best way to achieve this, if possible, without having to install anything new, so that it works on any system I want to use from the get go – Yuki.kuroshita Sep 12 '18 at 16:44
  • You may want to specify those requirements more clearly in the question. For instance, many systems covered by U&L.SE don't have bash or seq installed (those are GNU software found by default on GNU systems but few others). tput is a standard command, but tput cols is not standard... – Stéphane Chazelas Sep 12 '18 at 16:51
  • Oh okay. I did not know this. But let's just say, all the computers I want to use it on have at least everything I have used already available to them. I just want this script to be usable on about the 30 Debian based systems in my lab. – Yuki.kuroshita Sep 12 '18 at 16:59
  • And as I said, I might install some packages to do this if I need to. But here I was hoping for somethng not dependent on packages, if possible obviously. – Yuki.kuroshita Sep 12 '18 at 17:03
3

Maybe, but I think it's going to be harder than you expect.

You can make the cursor move around the terminal by outputting ANSI control codes. For more on them, see here.

In principle, you could move the cursor to your progress bar, add an =, and then move it back to wherever you plan to print output. Then on the next = you'd have to do it again... but this would probably be ugly.

If you're willing to depart from bash, you can probably find libraries that make this kind of thing easier (like this).

  • I don't really want to depart from bash if possible. I will try what you suggested, see if it works for me. – Yuki.kuroshita Sep 12 '18 at 16:46
  • I did what you suggested, it worked. A lot of putting the cursor somewhere and then putting it back. Maybe I should just use something else as everyone is suggesting. But since this is exactly what I asked, accepted. – Yuki.kuroshita Sep 12 '18 at 17:36
  • 1
    I also wrote an example script to check this. Should I post it here? – Yuki.kuroshita Sep 12 '18 at 17:37
  • I usually put example code in a github gist so that the link fits nicely in a comment. Alternatively, you can answer your own question and post the bash directly. Either way, somebody else might have you same question and benefit from seeing your code, so if you bothered to write it you might as well post it somewhere. – MatrixManAtYrService Sep 12 '18 at 18:27
  • Did post an answer, for what its worth – Yuki.kuroshita Sep 12 '18 at 20:11
4

I wrote a testing script to try and do what @MatrixManAtYrService suggested. I realized that this solution does not apply to all systems covered by U&L SE, but this works within the specifications I asked for.

#!/bin/bash

# go to last line and print the empty progress bar
tput sc #save the current cursor position
tput cup $((`tput lines`-1)) 3 # go to last line
echo -n "[" # the next 5 lines just print the required stuff to make the bar
for i in $(seq 1 $((`tput cols`-10))); do
    echo -n "-"
done
echo -n "]"
tput rc # bring the cursor back to the last saved position


# the actual loop which does the script's main job
for i in $(seq 0 10 100); do
    # print the filled progress bar
    tput sc  #save the current cursor position
    doned=${i}  #example value for completed amount
    total=100   #example value for total amount

    doned=`echo $doned $total | awk '{print ($1/$2)}'` # the next three lines calculate how many characters to print for the completed amount
    total=`tput cols | awk '{print $1-10}'`
    doned=`echo $doned $total | awk '{print int(($1*$2))}'`


    tput cup $((`tput lines`-1)) 4 #go to the last line
    for l in $(seq 1 $doned); do #this loop prints the required no. of "="s to fill the bar
        echo -n "="
    done
    tput rc #bring the cursor back to the last saved position

    # the next 7 lines are to find the row on which the cursor is currently on to check if it 
    # is at the last line 
    # (based on the accepted answer of this question: https://stackoverflow.com/questions/2575037/)
    exec < /dev/tty
    oldstty=$(stty -g)
    stty raw -echo min 0
    tput u7 > /dev/tty
    IFS=';' read -r -d R -a pos
    stty $oldstty
    row=$((${pos[0]:2} - 1))


    # check if the cursor is on the line before the last line, if yes, clear the terminal, 
    # and make the empty bar again and fill it with the required amount of "="s
    if [ $row -gt $((`tput lines`-2)) ]; then
        clear
        tput sc
        tput cup $((`tput lines`-1)) 3
        echo -n "["

        for j in $(seq 1 $((`tput cols`-10))); do
            echo -n "-"
        done
        echo -n "]"
        tput cup $((`tput lines`-1)) 4
        for k in $(seq 1 $doned); do
            echo -n "="
        done
        tput rc
    fi

    # this is just to show that the cursor is behaving correctly
    read -p "Do you want to continue? (y/n)" yn;  

done

 # the next few lines remove the progress bar after the program is over   
tput sc # save the current cursor position
tput cup $((`tput lines`-1)) 3 # go to the line with the progress bar
tput el # clear the current line
tput rc # go back to the saved cursor position

There must be better ways to handle overflow into the last line, instead of clearing the terminal, among other things. This serves more as a proof of concept of what @MatrixManAtYrService suggested. Any improvements or comments on its limitations are welcome

1

An easy way to get progress bars in a shell script is to use whiptail or dialog, as mentioned in comments. One or both should be available on most linux distros.

OP mentioned the aptitude installer in comments. Aptitude is a binary which uses the ncurses library, as does dialog.

  • I finally did use curses for doing the actual work, which is why I accepted Matrixman's answer. I had just asked for a solution which uses no extra tools. And I did not want a separate buffer for the loader, which is why I did not want to use dialog or whiptail. – Yuki.kuroshita Jan 23 at 23:18
  • Upvoted because of the information about aptitude – Yuki.kuroshita Jan 23 at 23:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.