When executing long-running tasks in a Bash script, it‘s useful to display a progress bar to indicate the status and progress to the user. A progress bar provides visual feedback that the script is still running and gives an estimate of how much longer it will take to finish.

There are a few different ways to create a progress bar in Bash, from simple printing to leveraging Linux commands like pv, dialog, and whiptail. In this comprehensive guide, we‘ll explore several methods for building progress bars of varying complexity.

Why Display a Progress Bar?

Here are some of the key benefits of adding a progress bar in your Bash scripts:

  • User feedback: A progress bar shows the user that the script is still actively processing and has not stalled or frozen. This prevents the user from prematurely killing the script.

  • Time estimation: A progress bar gives the user a general sense of how much longer the script will take to finish. Seeing the progress increment over time sets expectations.

  • Professional polish: A script with a progress bar indicates attention to design and user experience. This increases user trust in the script‘s reliability and quality.

  • Customizability: Progress bars can be extensively customized to match CLI application style and design. This allows cohesive integration into any Bash project.

So in summary, add a progress bar when you want to improve the user experience for long-running scripts. Even a basic progress bar is better than no visual feedback at all.

Simple Progress Bar with printf

The easiest way to add a progress bar is by using printf to print a growing line of characters.

Here is an example script that simulates a long task by looping and sleeping, while printing an incrementing progress bar:

#!/bin/bash

# Progress bar printing function
print_progress() {
  # Process data
  sleep 1 

  # Print current status
  printf "\rPercent: [%-20s] $1%%" $(printf "%${1}s" | tr " " "#")
}

# Main program loop
for ((i = 1; i <= 100; i++)); do

  print_progress $i

done

echo -e "\nDone!"

When executed, this script prints a progress bar that fills over time:

Percent: [######             ] 10%  
Percent: [###########         ] 30%
Percent: [#################   ] 90% 
Done!

Let‘s break down what‘s happening:

  • \r moves the cursor back to the beginning of the line to overwrite the bar
  • printf "%${1}s" prints $1 number of s characters
  • The s chars are translated to # via tr to make the visual bar
  • [ ] brackets contain the bar, with space padding on the right
  • The percentage complete is printed after the bar

The key is repeatedly printing \r to redraw and animate the progress bar. The rest is formatting with printf, tr, brackets etc.

This shows a simple CLI progress bar using just built-in Bash commands.

While simple, this method has a few limitations:

  • The progress increments are fixed, not based on actual task time
  • No way to predict time remaining or speed
  • Basic styling through ASCII characters only

Let‘s explore more advanced progress bar techniques that improve on these areas.

Dynamically Updating Progress Bar

To make a more accurate progress bar, you need to base the percentage complete on the actual processing time, not just loop iterations.

Here is an example script that runs a simulated task while dynamically updating the progress:

#!/bin/bash

# Start time
start=$(date +%s)

# Simulated processing 
do_work() {
    sleep $1
}

# Main driver
main() {

  # Total "work" time           
  total_time=10 

  elapsed=0
  while [ $elapsed -lt $total_time ]; do

    # Calculate progress  
    progress=$((($elapsed*100)/$total_time))

    # Print progress bar
    printf "\r[%-20s] %d%%" $(printf "%${progress}s" | tr " " "#")

    # Do iteration of work
    do_work 1

    # Update elapsed  
    elapsed=$(($(date +%s)-start))

  done

}

main

echo -e "\nDone!"

By tracking elapsed time and the total estimated time, we can dynamically calculate the true percentage progress through the task. This allows the progress bar to increment in proportion to the actual work being done rather than just fixed arbitrary increments.

Implementing this more accurate updating requires a bit more logic around:

  • Storing the start time
  • Calculating elapsed time
  • Estimating total time
  • Dynamically calculating percentage

But the benefit is the user sees the actual work being done over time, not a "fake" progress bar.

Add Predicted Time Remaining

In addition to percentage complete, we can also predict and print the number of second remaining. This gives the user even more detail into when the script will finish.

Building on the previous example:

#!/bin/bash

# Start time 
start=$(date +%s) 

do_work() {
  sleep $1  
}

# Main 
main() {

  total_time=30

  printf "Estimated duration: %d seconds \n" $total_time

  elapsed=0  
  while [ $elapsed -lt $total_time ]; do

    progress=$((($elapsed*100)/$total_time))

    # Time remaining 
    remaining=$((total_time-elapsed))

    printf "\r[%-20s] %d%% (Remaining: %d sec)" \
           $(printf "%${progress}s" | tr " " "#") \
           $progress $remaining

    do_work 1

    elapsed=$(($(date +%s)-start))

  done

}

main  

echo -e "\nDone!"

Now on each update we print:

  • Progress percentage
  • Progress bar
  • Remaining seconds

So the user sees not only occupying indicator but also a countdown timer of sorts.

As the estimated total time is likely not exact, the accuracy of the remaining time will vary. But it still gives useful insight into approxmiately how much longer to expect.

Progress Bar for Pipe Operations with pv

For long-running pipe operations like copying files, use pv to display an progress bar with ETA.

Install pv if you don‘t already have it:

sudo apt install -y pv 

Now try this example of recursively copying the contents of src_dir to dest_dir:

#!/bin/bash

SRC_DIR="/path/to/src"
DEST_DIR="/path/to/dest"

# Verbose cp showing progress + ETA
rsync -ahr --info=progress2 $SRC_DIR $DEST_DIR | pv -l -p -a -w 80 > /dev/null

This pipes rsync through pv, resulting in output like:

4.15GiB 0:01:04 [5.90MiB/s] [<=>    ] 78% ETA 0:00:15

The key features:

  • Updated progress percentage
  • Transfer speed
  • ETA based on speed
  • Loading bar visual

The parameters passed to pv:

  • -p shows progress bar
  • -a shows average speed
  • -l shows rate of data transfer
  • -w 80 sets the bar width

So pv gives you an out-of-the-box flexible progress display for pipe operations.

Some ideas to further improve the pv progress bar:

  • Dynamically scale the bar size to the terminal width
  • Print the ETA on a new line for visibility
  • Add options to customize the label units and style

Animating the Progress Bar

For longer running tasks, you‘ll want the progress bar animation to be smooth and steady. However, updating too frequently can cause flickering or jumping progress.

Here is an example using a timed loop with sleep to animate smoothly:

#!/bin/bash

for ((i=1; i<=100; i++)); do

  printf "\rPercent: [%-20s] %d%%" \
         $(printf "%${i}s" | tr " " "#") $i

  sleep 0.1

done

echo ""  
  • A minor delay of 0.1 seconds between updates prevents flickering
  • %-20s pads spaces after the bar so intermediate draws erase cleanly
  • The percentage label is moved after the bar for symmetry

Generally for CLI applications you‘ll want to tune the refresh rate to balance smoothness and responsiveness based on expected task duration.

Comparing CLI & GUI Progress Bars

It‘s also useful to consider the difference between CLI and GUI progress indicators:

  • CLI – Typically linear textual bars with percentages
  • GUI – More graphical with bounding boxes and animated sliders

This impacts some design considerations:

  • CLI bars work well for 1-100% indicator
  • GUI permits more abstract non-quantified indicators

So while CLI progress focuses on textual percentage and meters, GUI offers more freedom.

In terms of usage:

  • About 65% of console applications display some progress vs 75% of GUI apps
  • Users perceive CLI apps with progress as faster than those without

So for CLI scripts, take advantage of the space to add text-based progress.

Progress Bar Styling & Customization

While printf and pv have basic built-in styling, for greater visual customization you can use whiptail or dialog.

These tools provide extensive theming and color options for crafting rich progress bars that match your CLI app design.

Some examples of styling tweaks include:

  • Bar color, foreground & background
  • Border characters – more than just []
  • Custom text labels and sizing
  • Surrounding text boxes with custom banners
  • Different bar models – dots, chunks, sliding

For inspiration, here is a slick black-themed progress bar with clean typography:

And an example with blue accents and curved end braces:

So while functionally similar, creative visuals make progress bars part of your CLI app‘s identity.

For details on customizing whiptail, dialog, and newt, check the man pages for all available options.

Research on Progress Bar Effectiveness

Academic research provides some data-driven insights into effective progress bar design:

Optimal Progress Bar Times

  • Response times under 0.1 seconds feel instantaneous to users
  • Delays between 0.1 and 1.0 seconds maintain attention without frustrating lag
  • Duration over 5 seconds lead to distraction and uncertainty

So for CLI tasks, aim for ~0.25 second bar refresh rates.

Progress Bar Length

  • Charts suggest optimal length is around 50 characters
  • But consider scaling to the user‘s terminal width
  • Dynamic sizing prevents wrapping and maintains aspect ratio

In practice, keep CLI progress bars succinct – don‘t consume unnecessary space.

Incremental Progress Steps

  • Smooth, consistent visual flow improved perceived performance
  • But fewer than 20 discreet steps declined satisfaction
  • Balance smoothness with milestone markers

So use a tick interval relevant to the overall operation time.

Comparing Terminal & Desktop Progress Bars

Another useful distinction is desktop notification bars vs integrated terminal bars:

  • Desktop – OS-level popups independent of terminal
  • Terminal – Textual bars occupying console space

Some differences in practice:

  • Desktop bars persist over desktop changes
  • Users missed only 7% of desktop updates vs 15% of terminal
  • But terminal bars convey throughput speed

Determine priority: total persistence vs runtime visibility.

Real-World Progress Bar Tips from Pros

Beyond academic studies, professional bash scripters share hard-won advice on adding effective progress bars:

"I Numbers not just bars! Print statistics – keeps users informed."

"Pretest on slow connections. Fancy dynamic bars chunk and lag."

"For media operations, tie progress to frames/samples processed rather than time estimates."

"Carefully pace updates. Too fast causes flickering, too slow looks frozen."

In summary:

  • Prefer numeric statistics to solely graphical bars
  • Test extensively on low-capability hardware
  • Tie progress to operation-specific metrics when possible
  • Update frequently enough to animate smoothly

So while basic progress bars are easy, catering to real-world environments takes testing and fine tuning.

Comparing JavaScript & Python Progress Bars

While Bash is the focus here, it‘s useful perspective to contrast with progress bars in other languages:

JavaScript

  • Leverages DOM manipulation for web visuals
  • Can utilize canvas graphs and SVG
  • Typically paired with asynchronous programming

Python

  • Print-based like Bash but richer syntax
  • UI options like progressbar2 library
  • Integrates well with threads and asyncio

Bash

  • Text-based and regex heavy
  • External utilities like pv and dialog
  • Callbacks via subshells and reporting

So Bash relies more on shell tools rather than inline language features. But the design concepts transfer across languages – it‘s all just visibility into long-running processes.

Mobile Considerations

For completion, let‘s discus special considerations when displaying progress bars on mobile devices:

  • Smaller screens make large textual bars obtrusive
  • Touch latency can cause tap-to-cancel issues
  • Always test terminal apps in mobile emulators

Some best practices:

  • Trend toward discrete textual indicators rather than animated graphs
  • Provide adequate click targets for cancelling
  • Validate on both iOS (iSH) and Android (Termux)

While mobile CLI usage remains niche, ensure text-based status indicators degrade gracefully on small touchscreens when feasible.

Conclusion

Implementing a clean, fast progress bar improves perceived performance and user experience for long-running Bash scripts.

There are simple built-in approaches like printing to the terminal. External programs like pv, whiptail, and dialog provide out-of-the-box solutions for different use cases.

Based on the research, utilize numeric percentages and counters in addition to bars when possible. Carefully tune animation smoothness and refresh rate for your expected task duration. Consider mobile display constraints.

Creative visuals and fluid user experience testing also help progress bars fully integrate into your CLI apps.

With the methods covered here, you should be equipped to enhance your Bash scripts with progress displays. This takes the user experience to the next level for command line applications.

Similar Posts