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.
#!/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
echoOn a terminal this scrolls in place; captured literally you still see the final frame:
[####################] 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:
#!/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 >&2This 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:
# Debian/Ubuntu
# sudo apt install pv
pv -s "$(stat -c%s big.bin)" <big.bin >big.copystat 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:
# sudo apt install dialog
(
for p in $(seq 0 100); do
echo "$p"
sleep 0.02
done
) | dialog --title "Job" --gauge "Working…" 8 60 0The 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,\rbars 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 shrinkbar_width. - Integer math:
current*width/totaltruncates; for huge totals useawkorbcif you need smoother motion. - Speed: redrawing every iteration of a tight inner loop can dominate CPU—update every N records instead.
Related
- bash while loop for long-running work loops.
- bash concatenate strings when building bar characters.
- echo and printf for escapes and newlines.
- Further reading: How can I create a progress bar in the console?
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.

