Linux - Using a progression bar in your script
Add a visual progression bar to your Bash scripts on Linux. This guide shows you how to easily implement a progress bar to track concurrent jobs, visualize progress, and improve your terminal user interface.
This article is over a year old but was reviewed on July 30, 2026 — the content is still accurate.
This article is part of the Writing better Bash scripts series:
- Bash - ASCII art
- Bash - Loading environment variables from a file
- Bash - Script to add logging features to your script
- Bash - Echo on the console and in a logfile in the same time
- Linux - Compare two versions of the same script
- Linux - Sort functions in a Bash script
- Linux - Generate documentation from Bash scripts
- Linux - Take advantage of the number of CPUs you have; start concurrent jobs
- Linux - Using a progression bar in your script
- Linux - Bash scripts - Running unit tests with bats/bats

This follow-up article adds a visual progress bar to Bash scripts (building on the earlier parallel-jobs post) using a reusable progress_bar.sh helper: source it, pipe your loop's output through progress_bar::process "Doing some stuff" 50, and echo Progress=N from inside the loop to drive the bar — useful for long-running batch jobs like CSV-driven API uploads.
In my previous article; Linux - Take advantage of the number of CPUs you have; start concurrent jobs, we've seen how to start jobs in parallel.
The next cool thing is to show a progression bar in your console. This has a number of advantages, including a clear view of what's been done and what's still to be done, as well as an attractive interface.
When the number of items isn't known in advance, a live counter is the better fit; see Keep running and count the number of files in a folder using inotifywait. And to keep a trace of what happened once the bar is gone, Bash - Script to add logging features to your script.
Months ago, I've found this French blog post in my RSS feeds: https://xieme-art.org/post/bash-avance-barre-de-progression/ and, just, wow!!!
Let's play with it.
Result

That's a plain Bash loop, no external tool, driving a live progress bar in the console.
Why it works
There are three things a script needs to do to get this bar:
- Source the
progress_bar.shscript (credits to ephase); - Run the function that does the actual work in a specific way; not just
mainbutmain > >(progress_bar::process "Doing some stuff" 50). The figure50should be set to the number of occurrences the loop will run; - From inside that function,
echoone thing to the console:Progress=followed by a number (from 1 till 50) — that's the signal the bar reads to advance.
Not so difficult, right?
Create demo.sh
First, please create a new folder on your disk and jump into that folder: mkdir /tmp/progress && cd $_.
Then, create a demo.sh file in the folder and copy/paste the bash script below:
#!/bin/bashset -euo pipefail
function demo { echo "Progress=$1" sleep 0.1 return 0}
function main { for i in {1..50}; do demo "$i" done}
source "progress_bar.sh"
main > >(progress_bar::process "Doing some stuff" 50)This script defines a demo function that outputs progress messages, then runs it 50 times in a loop. The output gets piped through a progress bar handler from another script, which displays a nice formatted progress indicator while the work happens.
Make the script executable by running chmod +x ./demo.sh.
Create progress_bar.sh
Here is the content you'll need to copy/paste into a file called progress_bar.sh, created in the same folder as the demo.sh one.
Please create a new file on your hard disk; name it progress_bar.sh and copy/paste the content below into it.
#!/usr/bin/env bash
# region - Intro block -----------------------------------------------## Progress bar## ## How to use this script?## Just include this script in your own Bash like this `source progress_bar.sh`## Now, you can use any public functions defined here like `progress_bar::draw``.## ## Example## The example below will just loop for 1 till 50 and the most important part is the "Progress=${i}"# echo statement. The progression of the bar depends on a regex defined in the progress_bar::process() function.## ```bash# function main() {# for i in {1..50}# do# echo "Progress=${i}"# sleep 0.1# done# }## main > >(progress_bar::process "Doing some stuff" 50)# ```## ## Namespace## `progress_bar::`## cspell:ignore endregion## shellcheck disable=SC1090,SC1091## ## Source## The source comes from a French blog post:# https://xieme-art.org/post/bash-avance-barre-de-progression/## shellcheck disable=SC2059## endregion - Intro block --------------------------------------------
# let script exit if an unset variable is used or if a command failsset -o nounsetset -o errexit
# The different bar to display (done)PROGRESS_BAR_CHAR=(█ ▒)
# Show or not the introduction text (f.i. "Doing some stuff")PROGRESS_BAR_DISPLAY_INFO=1
PROGRESS_BAR_DISPLAY_COMP=1
# Display templatePROGRESS_BAR_INFO_TEMPLATE=' %s [ %3d/%3d ] 'PROGRESS_BAR_COMP_TEMPLATE=' %3d%% 'PROGRESS_BAR_TEMPLATE='\033[1m%s%s\033[0m%s%s\r'
# region - private function progress_bar::__change_column_size## ## Description## This function will capture de WINCH signal. That one is fired by Linux when# the size of the window (i.e. the console) is updated so allow us to retrieve the# max. number of columns that can fit in the window.## endregionfunction progress_bar::__change_column_size() { printf >&2 "%${COLUMNS}s" "" printf >&2 "\033[0K\r" COLUMNS=$(tput cols)}
# region - private function progress_bar::__draw## ## Description## Draw the progress bar / progression## endregionfunction progress_bar::__draw() { # function parameters local -r progress=${1?"progress is mandatory"} local -r total=${2?"total elements is mandatory"} local -r info=${3:-"In progress"}
# function local variables local progress_segment local todo_segment local info_segment="" local comp_segment=""
if [[ ${PROGRESS_BAR_DISPLAY_INFO:-1} -eq 1 ]]; then printf -v info_segment "${PROGRESS_BAR_INFO_TEMPLATE}" \ "$info" "$progress" "$total" fi
local -r progress_ratio=$((progress * 100 / total)) if [[ ${PROGRESS_BAR_DISPLAY_COMP:-0} -eq 1 ]]; then printf -v comp_segment "${PROGRESS_BAR_COMP_TEMPLATE}" \ "$progress_ratio" fi
# progress bar construction # calculate each element sizes, bar must fit in ou screen local -r bar_size=$((COLUMNS - ${#info_segment} - ${#comp_segment})) local -r progress_segment_size=$((bar_size * progress_ratio / 100)) local -r todo_segment_size=$((bar_size - progress_segment_size)) printf -v progress_segment "%${progress_segment_size}s" "" printf -v todo_segment "%${todo_segment_size}s" ""
printf >&2 "$PROGRESS_BAR_TEMPLATE" \ "$info_segment" \ "${progress_segment// /${PROGRESS_BAR_CHAR[0]}}" \ "${todo_segment// /${PROGRESS_BAR_CHAR[1]}}" \ "$comp_segment"}
# region - public function progress_bar::process## ## Description## Display a progress bar## ## Example## ```bash# function main() {# for i in {1..50}# do# echo "Progress=${i}"# sleep 0.1# done# }## main > >(progress_bar::process "Doing some stuff" 50)# ```## ## Parameters## * @arg $1 The text to display like "Upload in progress"# * @arg $2 The number of positions in the bar (f.i. if there are 4 rows to proceed, it has to be initialize to 4)## endregionfunction progress_bar::process() { local -r action="${1:-Upload in progress}" local -r max=${2:-100}
# Capture the resize of the window so the progress bar becomes responsive trap progress_bar::__change_column_size WINCH
while read -r line; do # Each line with "Progress=(digit)" will increase the progress bar if [[ $line =~ Progress=([[:digit:]]{1,}) ]]; then progress_bar::__draw "${BASH_REMATCH[1]}" "${max}" "${action}" fi done}
# This script didn't contains executable code; only helpers.sourced=0(return 0 2> /dev/null) && sourced=1 || sourced=0if [ "${sourced}" -eq "0" ]; then printf "\033[1;31m%s\033[0m\n" "ERROR, the $0 script is meant to be sourced. Try 'source $0' and use public functions." >&2 # Write on stderr exit 1fi
return 0This is a reusable Bash library for drawing a progress bar. You source it into another script, then pipe output containing "Progress=N" lines through the `progress_bar::process` function, which reads those lines, extracts the progress number, and draws an animated bar showing how far you've gotten.
Time to run the code
Simply return to the console and run ./demo.sh and... wow; that's pretty cool — you'll get the bar shown above.
In a real-life example, I've implemented this feature in a Bash script where I need to read a CSV file and for each record retrieved in the file, I need to run a curl statement for calling a RESTful POST API and upload a PDF.
And, when started in an interactive way, the screen output is really practical and terribly effective.
Of course, I'm also using a log file where I redirect a maximum amount of information.
This article is part of the Writing better Bash scripts series:
- Bash - ASCII art
- Bash - Loading environment variables from a file
- Bash - Script to add logging features to your script
- Bash - Echo on the console and in a logfile in the same time
- Linux - Compare two versions of the same script
- Linux - Sort functions in a Bash script
- Linux - Generate documentation from Bash scripts
- Linux - Take advantage of the number of CPUs you have; start concurrent jobs
- Linux - Using a progression bar in your script
- Linux - Bash scripts - Running unit tests with bats/bats
Loading comments…





