Bash progress bar: text bar, script implementation, `pv`, and `dialog`

A bash progress bar is almost always the same trick: print a line, then print \r (carriage return) so the next print overwrites the same row—your bash loading bar or shell script progress bar “animates” in a real terminal. When you know how many bytes move through a pipe, pv is the fastest linux progress bar for copies and compressions; when you want a widget, dialog --gauge is the usual TUI choice. For GUI-style bars in Python, see Python progress bars.

I re-ran the Bash snippets on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37. A text bar only looks right on a TTY; see the pitfalls section for logs and CI.


Bash progress bar: minimal \r implementation

echo -ne '\r...' (or printf '\r...') redraws one row. Below, total is how many steps you own; p walks 0..total.

bash
#!/usr/bin/env bash
total=10
for ((p = 0; p <= total; p++)); do
  filled=$((p * 20 / total))
  bar=''
  for ((i = 0; i < filled; i++)); do bar+='#'; done
  for ((i = filled; i < 20; i++)); do bar+='-'; done
  pct=$((p * 100 / total))
  printf '\r[%s] %3d%%' "$bar" "$pct"
  sleep 0.05
done
echo

On a terminal this scrolls in place; captured literally you still see the final frame:

text
[####################] 100%

That is the core of progress bar in bash tutorials: width × (current/total) for filled cells, then % from the same fraction.


bash script progress bar implementation (function + stderr)

Wrap the math once, send the bar to stderr so ./script.log stays clean when users redirect stdout:

bash
#!/usr/bin/env bash
set -euo pipefail

bar_width=40

draw_bar() {
  local current=$1 total=$2 label=${3:-}
  [[ $total -gt 0 ]] || return 0
  local filled=$((current * bar_width / total))
  local i bar=''
  for ((i = 0; i < filled; i++)); do bar+='#'; done
  for ((i = filled; i < bar_width; i++)); do bar+='-'; done
  local pct=$((current * 100 / total))
  printf '\r[%s] %3d%% %s' "$bar" "$pct" "$label" >&2
}

items=(alpha bravo charlie delta)
n=${#items[@]}
for ((idx = 0; idx < n; idx++)); do
  draw_bar "$idx" "$((n - 1))" "processing ${items[idx]}"
  sleep 0.15
done
echo >&2

This pattern answers bash script progress bar implementation searches: one function, one loop, predictable total.


Multi-step jobs and a “bash loading bar”

Shell script progress bar code for three phases is just reset current/total (or call draw_bar with phase-local totals) between phases—no magic. A bash loading bar with unknown duration often uses an indeterminate spinner instead of a percent bar; if you still want a bar, map each phase to a slice of 0–100 (for example 0–33, 34–66, 67–100).


pv: linux progress bar for pipes (byte counts)

When data flows through a pipe and you know the size, pv draws a progress bar bash users recognize without hand-rolled math:

bash
# Debian/Ubuntu
# sudo apt install pv

pv -s "$(stat -c%s big.bin)" <big.bin >big.copy

stat options differ on macOS/BSD; adjust for portability. pv also fits gzip and mysqldump pipelines—any long stream with a predictable size or rate goal.


dialog --gauge (full-terminal widget)

For a shell progress bar bash can delegate to dialog when curses-style UI is OK and DISPLAY may be empty:

bash
# sudo apt install dialog

(
  for p in $(seq 0 100); do
    echo "$p"
    sleep 0.02
  done
) | dialog --title "Job" --gauge "Working…" 8 60 0

The subshell prints integers 0–100 on stdout; dialog maps that to the gauge.


TTY checks, logs, and pitfalls

  • Not a TTY: in CI or script | tee, \r bars may look like garbage. Guard with [[ -t 2 ]] before drawing, or print plain one-line percentages instead.
  • Line length: terminals wrap if your bar + label exceeds columns—read COLUMNS (default 80) and shrink bar_width.
  • Integer math: current*width/total truncates; for huge totals use awk command or bc if you need smoother motion.
  • Speed: redrawing every iteration of a tight inner loop can dominate CPU—update every N records instead.


Summary

A bash progress bar in the console is usually printf '\r[####------] 40%' style output on a TTY, recomputing filled width from current/total. A solid bash script progress bar implementation wraps that in a function, prints to stderr, and picks bar_width with COLUMNS in mind. For shell script progress bar work on pipes, pv -s is the pragmatic linux progress bar when sizes are known; dialog --gauge covers shell progress bar bash cases that need a simple TUI. Skip fancy bars when stdout is not a terminal—logs and CI deserve plain progress lines instead.

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)