1. Overview
A progress bar can straightforwardly show the user how a task execution is going. For example, it can report how much processing (usually in percentage) has been done so far.
In this tutorial, we’ll learn to implement a command line progress bar using shell script.
2. The Requirements
A typical command line progress bar may look like this:
Progress : [#################--------------------------------] 35.25%
So, it comprises two parts: a textual progress bar and a percentage value. Further, the progress bar should refresh itself. In other words, the output should always overwrite the same output line.
To make the progress bar easier to be customized, we should allow the user to set some attributes, such as the size of the bar, the scale of the percentage value, and the characters on the bar to indicate done and todo, which is ‘*#‘ and ‘–*‘ in the example above.
Apart from that, the essential requirement would be reusability. Ideally, our progress bar will be a function. Users can call it without any modification to show the progress information, no matter which kind of task they’re working with.
Finally, let’s summarize the requirements we need to achieve:
- refreshing the same line
- customizable – bar size, “done” and “todo” characters on the bar, and the percentage scale
- reusable – easy to be called by various tasks without modifying the code
3. The Implementation Idea
Before we dive into the implementation, let’s first analyze the requirements and consider how to achieve them.
To make the progress bar always overwrite the same output line, we can put the carriage return escape sequences (\r) in the printf command or the echo command with the -n and -e options.
We can also declare several top-level variables and make them user-changeable attributes to make the progress bar customizable. Of course, we’ll assign them default values.
If we think about the progress calculation, no matter which kind of task the user is working on, two values are required to calculate the progress:
- the total value in number – the total number of tasks that need to be done. For example, for upload or download tasks, it could be the total number of bytes required to upload or download.
- the current value in number – the number of tasks done.
Thus, we can derive these:
- current / total: done ratio (DR)
- bar_size * DR: the number of done characters in the bar (DoneNR)
- bar_size – DoneNR: the number of todo characters in the bar
Therefore, we can create a show_progress function and let it accept the current and the total arguments.
Now, it’s time to start the implementation
4. The Implementation
To make it easier to understand, let’s first see the script code and then see how it works:
#!/bin/bash
bar_size=40
bar_char_done="#"
bar_char_todo="-"
bar_percentage_scale=2
function show_progress {
current="$1"
total="$2"
# calculate the progress in percentage
percent=$(bc <<< "scale=$bar_percentage_scale; 100 * $current / $total" )
# The number of done and todo characters
done=$(bc <<< "scale=0; $bar_size * $percent / 100" )
todo=$(bc <<< "scale=0; $bar_size - $done" )
# build the done and todo sub-bars
done_sub_bar=$(printf "%${done}s" | tr " " "${bar_char_done}")
todo_sub_bar=$(printf "%${todo}s" | tr " " "${bar_char_todo}")
# output the bar
echo -ne "\rProgress : [${done_sub_bar}${todo_sub_bar}] ${percent}%"
if [ $total -eq $current ]; then
echo -e "\nDONE"
fi
}
So next, let’s walk through the script and understand how the function works.
First, we create four bar_* variables with default values outside the major function. Then, if required, the user can customize the progress bar by setting these variables with different values.
Then comes the core part, the show_progress function. As we’ve discussed earlier, the function receives two arguments: total and current. For simplicity, we’ve omitted all variable validations in the script.
Next, we’ll start the calculation. There are different ways to do arithmetic calculations in shell scripts. As we need to handle decimal numbers, we choose the bc command as the calculator in the script.
First, we calculate the done ratio in percentage: percent=$(bc <<< “scale=$bar_percentage_scale; 100 * $current / $total” ). Here, we feed the bc command by a here-string with the user-defined scale option.
Similarly, we calculate the number of done and todo characters required in the progress bar. It’s worth mentioning that we don’t apply the user-defined scale to these two calculations. Instead, as the code above shows, we use the hardcoded scale=0. This is because these two counts require two integers. scale=0 will explicitly tell bc to chop the decimal part. Further, it’ll overwrite the default scale setting in shell users’ bc configuration ($HOME/.bcrc).
Next, we build up the two sub-bars. We create each sub-bar in two steps: printf “…” and tr “…”.
First, the printf “%${n}s” command prints n spaces, for example:
$ printf "%3s" | wc -c
3
$ printf "%7s" | wc -c
7
Then, the tr command converts each space character to the required one, which is the character of done or todo, for example:
$ printf "%3s" | tr " " "#"
###
$ printf "%7s" | tr " " "-"
-------
So far, we have every part of the progress bar. So we assemble and output them using the echo command with the -n and -e options.
Finally, in case current == total, all tasks are completed. We print “DONE” in a new line.
5. Testing the progress_bar.sh Script
To use our progress_bar.sh in other scripts is pretty simple. We only need to source the progress_bar.sh script and call the show_progress() function.
Next, let’s create a script to perform some tasks and show the execution progress:
$ cat heavy_work.sh
#!/bin/bash
source progress_bar.sh
tasks_in_total=37
for current_task in $(seq $tasks_in_total)
do
sleep 0.2 #simulate the task running
show_progress $current_task $tasks_in_total
done
As the heavy_work.sh script shows, we source the progress bar script at the beginning. So then, we’ve defined 37 tasks to do.
For simplicity, we “work” through all tasks in a for loop and use the sleep command to simulate the task execution. Of course, after each task is done, we call the show_progress() function to show the progress bar.
Now, let’s execute this script and see if the progress bar is rendered as expected:
As we can see in the demo, the progress bar works. However, this demo uses the progress bar script’s default settings. So next, let’s do some customizations and execute heavy_work.sh again:
#!/bin/bash
source progress_bar.sh
# bar customization
bar_size=70
bar_char_done="|"
bar_char_todo=" "
bar_percentage_scale=4
tasks_in_total=37
for current_task in $(seq $tasks_in_total)
...
As the demo shows, progress bar customization works too.
6. Conclusion
In this article, we’ve learned how to implement a customizable, reusable command line progress bar using shell script.