14

How can I create an progress bar with bash?

This is my script:

 #!/bin/bash
 pass='number1 number12 number13 number14 number15 number16'
 chk='number14'
 for i in $pass ; do
 if [ "$i" == "$chk" ]; then
 echo ' Found ^_^'
 else
 echo 'loading 50%'
 fi
 done

I want to replace echo 'loading 50%' with anything to create an progress bar.

| improve this question | | | | |
  • 1
    A progress bar in the terminal or a progress bar in a separate GUI window? – Byte Commander Mar 17 '16 at 15:50
  • 2
    In the terminal – Ghadeer R. Majeed Mar 17 '16 at 15:54
  • The eta might do what you want. – aioobe Jun 8 '19 at 0:14
14

whiptail comes preinstalled on Ubuntu and many other distros, and will show full-screen (but still terminal-based) progress elements.

dialog is a superset of whiptail, so this example will work equally well with both. It does provide more advanced UI elements, so it may come in handy if you're looking for user interaction such as file pickers and forms, but it has the disadvantage of not coming preinstalled on many systems.

whiptail

dialog

for i in $(seq 1 100)
do
    sleep 0.1 
    echo $i
done | whiptail --title 'Test script' --gauge 'Running...' 6 60 0

Note that the script output is interpreted as a percentage, so you may have to adjust your output accordingly.

Whiptail and Dialog also allow you to modify the text at run time via a rather cryptic syntax:

phases=( 
    'Locating Jebediah Kerman...'
    'Motivating Kerbals...'
    'Treating Kessler Syndrome...'
    'Recruiting Kerbals...'
)   

for i in $(seq 1 100); do  
    sleep 0.1

    if [ $i -eq 100 ]; then
        echo -e "XXX\n100\nDone!\nXXX"
    elif [ $(($i % 25)) -eq 0 ]; then
        let "phase = $i / 25"
        echo -e "XXX\n$i\n${phases[phase]}\nXXX"
    else
        echo $i
    fi 
done | whiptail --title 'Kerbal Space Program' --gauge "${phases[0]}" 6 60 0

pv shows the progress of a file or stream being piped through it. It cannot however be (easily?) used to show progress of a custom operation such as a loop. It's designed specifically for streams.

$ head -c 1G < /dev/urandom | pv -s 1G > /dev/null
 277MB 0:00:16 [17.4MB/s] [========>                           ] 27% ETA 0:00:43

Some real-world examples where pv comes in handy:

# progress while importing a DB dump
pv mybigfile.sql | mysql -uroot -p dbname

# importing straight from a remote server
ssh user@server 'cat mybigfile.sql.gz' | pv | gzip -cd | mysql -uroot -p dbname

# taking a snapshot of a btrfs partition
btrfs send /snapshots/$date | pv | btrfs receive /mnt/backup/root

I don't know of any commands that give one-line progress bars in the style of pv or wget, but there are plenty of simple Bash/Perl/sed scripts that will add that functionality, as others have shared here.

| improve this answer | | | | |
  • To show the process of a loop with pv you can either make it look for the loop's output or create some fake output, e.g. an echo in every iteration, pipe it to pv and give it the iteration count with -s. If it's not wanted, remember to redirect the loop's stdout to /dev/null. Here's an example showing this approach. – dessert Apr 24 '18 at 6:58
6

You can use zenity to create simple GTK dialog windows. One of the available options is a progress bar dialog.

You create such a window using zenity --progress. To make it useful, you should specify more information by adding some of the options below (excerpt from man zenity):

   Progress options
   --text=STRING
          Set the dialog text
   --percentage=INT
          Set initial percentage
   --auto-close
          Close dialog when 100% has been reached
   --auto-kill
          Kill parent process if cancel button is pressed
   --pulsate
          Pulsate progress bar
   --no-cancel
          Hides the cancel button

There are two modes:

  • pulsating: The progress bar is pulsating, it just indicates that something is running, but does not tell anything about the progress. You do this by setting the --pulsating option.

  • manual: You have to pipe the current progress percentage to the zenity command's standard input to update the progress bar.
    An example for this could look like that below. Note that the previous commands are grouped to a subshell so that all the output is redirected to the zenity dialog and not just that of the last command:

    (echo 10; sleep 2; echo 20; sleep 2; echo 50; sleep 2) | zenity --progress
    
| improve this answer | | | | |
  • Just in case this would also be an option. – Byte Commander Mar 17 '16 at 15:59
  • 1
    Sorry my dear , this is GUI progress bar windows , I want to create a progress bar in the terminal , for example i want to see it while the script is checking ==> [ ###########--------------] 52% – Ghadeer R. Majeed Mar 17 '16 at 16:39
  • 1
    Yes, I understand. It's just that I already had written half of my answer when you said that, so I just decided to post it anyway in case somebody else might need it in the future. I hope you don't mind, as there are quite a few terminal based solutions as well. – Byte Commander Mar 18 '16 at 7:06
5

This code will do it, and doesn't require anything (other than bash, of course). It prints # signs, like you asked in your comment:

pass='number1 number12 number13 number14 number15 number16'
chk='number14'
passarr=($pass)
lenProgressBar=${#passarr[@]}

echo -n '['
i=0

while [ $i -lt $lenProgressBar ]; do
    echo -n '-'
    ((i++))
done

echo -n ']'
i=0

while [ $i -lt $lenProgressBar ]; do
    echo -e -n '\b'
    ((i++))
done

echo -e -n '\b'
for i in $pass ; do
    if [ "$i" = "$chk" ]; then
        echo -e '#\nFound ^_^'
        break
    else
        echo -n '#'
    fi
done

However, if you have a lot to check, this will just fill your screen with # signs. To fix that issue, try this code:

lenProgressBar=5
pass='number1 number12 number13 number14 number15 number16'
chk='number14'
passarr=($pass)
lenPass=${#passarr[@]}

if [ $lenProgressBar -gt $lenPass ]; then
    lenProgressBar=lenPass
elif [ $lenProgressBar -lt 1 ]; then
    lenProgressBar=1
fi

let "chksForEqualsPrint = $lenPass / $lenProgressBar"
echo -n '['
i=0

while [ $i -lt $lenProgressBar ]; do
    echo -n '-'
    ((i++))
done

echo -n ']'
i=0

while [ $i -lt $lenProgressBar ]; do
    echo -e -n '\b'
    ((i++))
done

echo -e -n '\b'
n=1

for i in $pass ; do
    if [ "$i" = "$chk" ]; then
        echo -e '\nFound ^_^'
        break
    else
        if [ $n -eq $chksForEqualsPrint ]; then
            echo -n '#'
            n=1
        else
            ((n++))
        fi
    fi
done

Change the 5 in the first line (lenProgressBar=5) to the length that you want your progress bar to be. It'll take longer to print a # sign with lower length progress bars than with higher length ones, but don't let the length of the progress bar exceed your screen size; it won't work well if you do. (It won't let you use a progress bar higher than the number of items you're checking or lower than 1)

| improve this answer | | | | |
  • 1
    You could use tput cols to detect the width of the terminal window and scale the progressbar accordingly. – Mikkel Jul 18 '16 at 21:03
1

Here's another approach using ansi escape codes:

#!/bin/bash

pass='number1 number2 number 3 number4 number12 number13 number14 number15 number16'
chk='number15'
result="Not Found!"

echo
echo -n "Working... "
echo -ne "\033[1;32m\033[7m\033[?25l"

for i in $pass ; do
   sleep .4s
   if [ "$i" == "$chk" ]; then
      result="  Found ^_^"
      break
   else
      echo -n " "
   fi
done

echo -ne "\r\033[0m\033[K\033[?25h"
echo $result
echo
| improve this answer | | | | |

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.