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

Build a bash progress bar and bash loading bar in the terminal with carriage return redraws, reusable functions, multi-step jobs, plus pv for pipe byte counts and dialog for a gauge widget.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

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 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 …