Create a Progression Bar in Bash

We will show here how to create a bash script implementing a progression bar. Imagine you have a script creating automatically a bulk of images, this script takes time and while it is running you would like to know how far it is, how fast, and how long it still needs (easily adaptable for other cases like downloading script etc.). In brief, how to get something like that:

Progression Bar

Let’s go step by step:

Arguments

In this particular case, the script takes several arguments:

Initializing the progression loop

We initialize a while loop checking the progress at regular intervals until the the process monitored (here the images creation) is completed.

1
2
3
4
progress=0
n_images_previous=0
start_time=$( date +%s.%N )
while (( $(echo "${progress} < 100" | bc -l) )); do

Several variables are initialized:

In the condition of the while loop, we pipe a mathematical expression into bc | l. That is (IMHO) a good way to evaluate arithmetical expression in bash.

Estimating the progress speed

Physically: speed = distance / time. In the same vein here, the \(distance\) is the difference between the number of images created so far - the number of images that were created at the end of the previous loop. \(time\) is the runtime of the previous loop. Thus we get:

1
2
3
4
5
6
runtime=$( echo "${end_time} - ${start_time}" | bc -l )
speed=$(printf '%.1f\n' \
    $(echo "(${n_images_created} - ${n_images_previous}) \
    / ${runtime}" | bc -l))
# Re-initialize `n_images_previous` for the next loop:
n_images_previous=${n_images_created}

using bc -l as previously. [printf '%.1f\n' on l.2 allows us to control the precision of the result, here 1 decimal]

Estimating and displaying the Estimated Time Left (ETL)

How long have we roughly to wait until the monitored process is finished? We compute it as the distance left (here: number of images to be still created) divided by the current speed:

1
2
3
etl=$(printf '%.0f\n' \
    $(echo "${n_images_tocreate} / ${speed}" \
    | bc -l))

That’s the ETL in seconds. But we would like to have it in a more human-readable format, like 1h 23min 4s instead of 4984s. So we start with the hours:

1
2
3
4
n_hours=$(printf '%.0f\n' $(echo "${etl} / 3600" | bc -l))
if (( $(echo "${n_hours} >= 1" | bc -l) )); then
  etl_toprint="${n_hours}h "
fi

On l.1 we compute the ETL in hours rounded to the previous integer. If the result is greater or equal 1, we append it with the suffix h to the string to print. Thus we avoid displaying 0h ....

Similarly for the minutes:

1
2
3
4
5
n_min=$(printf '%.0f\n' \
    $(echo "scale=0; (${etl} % 3600) / 60" | bc -l))
if [[ ${n_min} -ge 1 || ${n_hours} -ge 1 ]]; then
  etl_toprint="${etl_toprint}${n_min}min "
fi

Remarks:

Final note

Run the process bar in the background by appending & at the end of the command just before running the process to monitor:

1
2
./progress-bar.sh -n 3000 &
./myscript-to-monitor.sh

The complete code is available in this snippet.

Thank you for reading!


Share this: