Skip to content

Bash Progress Bars


Basic Bash loop progress bar

for i in {1..100}; do printf "\r[%3d%%] %-${i}s" "$i" "#" | tr ' ' '#'; sleep 0.05; done; echo

For loop – simple percentage bar

for i in {1..100}; do
  printf "\rProgress: [%-50s] %3d%%" "$(printf '#%.0s' $(seq 1 $((i/2))))" "$i"
  sleep 0.05
done
echo

Use a for loop – simple percentage bar

#!/usr/bin/env bash
for i in {1..100}; do
  printf "\rProgress: [%-50s] %3d%%" "$(printf '#%.0s' $(seq 1 $((i/2))))" "$i"
  sleep 0.05
done
echo

Use a while loop – counter based

#!/usr/bin/env bash
i=0
while [ $i -le 100 ]; do
  filled=$((i/2))
  printf "\r[%.*s%*s] %3d%%" "$filled" "##################################################" "$((50-filled))" "" "$i"
  sleep 0.05
  ((i++))
done
echo

Use a until loop – run until complete

#!/usr/bin/env bash
i=0
until [ $i -gt 100 ]; do
  printf "\rProgress: %3d%%" "$i"
  sleep 0.05
  ((i++))
done
echo

Spinner (no percentage, task-based)

  • (Stop with Ctrl+C or kill from parent process.)
#!/usr/bin/env bash
spinner='|/-\'
i=0
while sleep 0.1; do
  printf "\r%c Working..." "${spinner:i++%4:1}"
done

Progress bar based on number of tasks

#!/usr/bin/env bash
tasks=(a b c d e f g h i j)
total=${#tasks[@]}
count=0

for task in "${tasks[@]}"; do
  ((count++))
  percent=$((count*100/total))
  printf "\r[%3d%%] %s" "$percent" "$task"
  sleep 0.3
done
echo

Use seq + for – compact style

#!/usr/bin/env bash
for i in $(seq 0 100); do
  printf "\rProgress: %3d%%" "$i"
  sleep 0.03
done
echo

File-based progress (bytes copied)

#!/usr/bin/env bash
src="bigfile"
size=$(stat -c%s "$src")

while true; do
  copied=$(stat -c%s "$src.copy" 2>/dev/null || echo 0)
  percent=$((copied*100/size))
  printf "\rCopying: %3d%%" "$percent"
  [ "$percent" -ge 100 ] && break
  sleep 0.2
done
echo

Use a read loop (pipe-driven progress)

#!/usr/bin/env bash
total=100
count=0

yes | head -n $total | while read; do
  ((count++))
  printf "\rProgress: %3d%%" $((count*100/total))
  sleep 0.05
done
echo

Use trap-safe progress bar function

progress() {
  for i in {1..100}; do
    printf "\r[%3d%%]" "$i"
    sleep 0.05
  done
}

trap 'echo; exit' INT
progress
echo

Unicode bar (modern terminals)

#!/usr/bin/env bash
blocks=("▏" "▎" "▍" "▌" "▋" "▊" "▉" "█")

for i in {1..80}; do
  printf "\rProgress: %s" "${blocks[i%8]}"
  sleep 0.05
done
echo

Use pv (Pipe Viewer) percentage bar

dd if=/dev/zero bs=1M count=100 2>/dev/null | pv -pt | dd of=/dev/null

Use pv with explicit size

pv -s 100M </dev/zero >/dev/null

Using yes + pv

yes | pv -l -s 1000 | head -n 1000 >/dev/null

Use dialog gauge (text UI progress bar)

for i in {1..100}; do echo $i; sleep 0.05; done | dialog --gauge "Working..." 7 50

Use whiptail gauge

for i in {1..100}; do echo $i; sleep 0.05; done | whiptail --gauge "Processing..." 6 50 0

Pure awk progress bar

awk 'BEGIN{for(i=1;i<=100;i++){printf "\r[%3d%%] %s",i,substr("##################################################",1,i/2); system("sleep 0.05")}}'

Use rsync built-in progress bar

rsync -a --info=progress2 /usr/bin/ /tmp/bin-copy/

Use dd with status progress

dd if=/dev/zero of=/dev/null bs=1M count=200 status=progress

ETA-aware progress bar (dynamic time remaining)

#!/usr/bin/env bash
total=100
start=$(date +%s)

for i in $(seq 1 $total); do
  now=$(date +%s)
  elapsed=$((now - start))
  eta=$((elapsed * (total - i) / i))
  printf "\r[%3d%%] Elapsed: %2ds | ETA: %2ds" "$i" "$elapsed" "$eta"
  sleep 0.05
done
echo

Adaptive-width progress bar (auto-fits terminal size)

#!/usr/bin/env bash
for i in {1..100}; do
  cols=$(tput cols)
  bar_width=$((cols - 10))
  filled=$((i * bar_width / 100))
  printf "\r[%s%s] %3d%%" \
    "$(printf '%*s' "$filled" | tr ' ' '#')" \
    "$(printf '%*s' "$((bar_width-filled))")" \
    "$i"
  sleep 0.05
done
echo

Color-gradient progress bar (true terminal candy)

#!/usr/bin/env bash
for i in {0..100}; do
  color=$((31 + i % 6))
  printf "\r\033[%sm█\033[0m %3d%%" "$color" "$i"
  sleep 0.04
done
echo

Signal-driven progress bar (external control)

#!/usr/bin/env bash
progress=0

trap '((progress+=10))' USR1
trap 'echo; exit' INT

while true; do
  printf "\rProgress: %3d%%" "$progress"
  [ "$progress" -ge 100 ] && break
  sleep 0.1
done
echo

Multi-stage pipeline progress bar (step-based)

#!/usr/bin/env bash
steps=("Download" "Verify" "Extract" "Compile" "Install")
total=${#steps[@]}

for i in "${!steps[@]}"; do
  percent=$(((i+1)*100/total))
  printf "\r[%3d%%] %s..." "$percent" "${steps[i]}"
  sleep 0.7
done
echo

Bash progress indicator with time estimation

count=0
total=34
pstr="[=======================================================================]"

while [ $count -lt $total ]; do
  sleep 0.5 # this is work
  count=$(( $count + 1 ))
  pd=$(( $count * 73 / $total ))
  printf "\r%3d.%1d%% %.${pd}s" $(( $count * 100 / $total )) $(( ($count * 1000 / $total) % 10 )) $pstr
done

Bash progress bar with elapsed time and remaining estimate

    count=0
total=34
start=`date +%s`

while [ $count -lt $total ]; do
  sleep 0.5 # this is work
  cur=`date +%s`
  count=$(( $count + 1 ))
  pd=$(( $count * 73 / $total ))
  runtime=$(( $cur-$start ))
  estremain=$(( ($runtime * $total / $count)-$runtime ))
  printf "\r%d.%d%% complete ($count of $total) - est %d:%0.2d remaining\e[K" $(( $count*100/$total )) $(( ($count*1000/$total)%10)) $(( $estremain/60 )) $(( $estremain%60 ))
done
printf "\ndone\n"

High-resolution Braille progress bar (8× smoother than blocks)

chars=(      )
total=100

for ((i=0;i<=total;i++)); do
  idx=$(( i * ${#chars[@]} / total ))
  printf "\rProgress: %s %3d%%" "${chars[idx]}" "$i"
  sleep 0.04
done
echo

Drop-in loop (use with any set above)

Chars Description
chars=(⣀ ⣄ ⣆ ⣇ ⣧ ⣷ ⣿) Braille-based left-to-right fill; very smooth perceived progress
chars=(⠁ ⠃ ⠇ ⠏ ⠟ ⠿ ⣿) Braille dot density ramp; excellent for subtle progress indication
chars=(░ ▒ ▓ █) Classic ASCII-style shading; highly compatible and readable
chars=(▏ ▎ ▍ ▌ ▋ ▊ ▉ █) Fine-grained vertical block fill; precise and fluid
chars=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █) Horizontal bar growth; ideal for traditional progress meters
chars=(▱ ▰ █) Sparse diagonal-style blocks; minimal but expressive
chars=(○ ◔ ◑ ◕ ●) Circular fill progression; visually intuitive completion cue
chars=(▢ ▣ ■) Square outline-to-solid fill; clean and structured look
chars=(· : ∙ • ●) Dot intensity ramp; subtle, lightweight progress feedback
chars=(╱ ╲ ╳ █) Cross-stroke animation; dynamic, spinner-like feel
chars=(⠂ ⠄ ⠆ ⠇ ⠧ ⠷ ⠿) Minimal braille ramp; smooth motion with low visual noise
chars=(⣁ ⣃ ⣇ ⣏ ⣟ ⣯ ⣷ ⣿) Heavy braille gradient; dense and highly visible fill
chars=(▖ ▘ ▌ ▛ █) Quadrant block buildup (left-biased); crisp stepwise growth
chars=(▗ ▙ ▐ ▜ █) Quadrant block buildup (right-biased); mirrored visual flow
chars=(▘ ▝ ▖ ▗ ▙ ▛ ▜ ▟ █) Full quadrant animation; very smooth and visually rich
chars=(╌ ╍ ╎ ╏ █) Line thickness ramp; looks like increasing stroke weight
chars=(┈ ┉ ┊ ┋ █) Dashed vertical density increase; rhythmic and readable
chars=(◌ ◔ ◑ ◕ ●) Alternate circle fill; softer and more decorative
chars=(⬒ ⬔ ⬕ ⬖ ⬗ ⬘ ⬙ ⬛) Geometric square morph; modern, UI-like appearance
chars=(▭ ▮ █) Minimal block fill; clean and fast visual feedback
chars=(   )   # swap with any set above
total=100

for ((i=0;i<=total;i++)); do
  idx=$(( i * (${#chars[@]} - 1) / total ))
  printf "\rProgress: %s %3d%%" "${chars[idx]}" "$i"
  sleep 0.04
done
echo

Dual-metric bar (percent + throughput)

total=200
start=$(date +%s)

for ((i=1;i<=total;i++)); do
  now=$(date +%s)
  rate=$(( i / (now - start + 1) ))
  printf "\r[%3d%%] %4d items/sec" $((i*100/total)) "$rate"
  sleep 0.03
done
echo

Waveform progress bar (oscillating animation)

bar="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
len=${#bar}

for i in {0..100}; do
  offset=$(( i % len ))
  printf "\r[%s] %3d%%" "${bar:offset}${bar:0:offset}" "$i"
  sleep 0.05
done
echo

Log-scaled progress bar (realistic for heavy workloads)

total=100

for i in $(seq 1 $total); do
  scaled=$(awk "BEGIN{printf \"%d\", log($i+1)/log($total+1)*100}")
  printf "\rProgress: %3d%% (realistic)" "$scaled"
  sleep 0.04
done
echo

Multi-line progress bar (redraws cleanly)

total=100
start=$(date +%s)

printf "\n\n"   # reserve 2 lines
for i in {1..100}; do
  elapsed=$(( $(date +%s) - start ))
  printf "\033[2A"                           # move up 2 lines
  printf "[%-50s] %3d%%\n" \
    "$(printf '#%.0s' $(seq 1 $((i/2))))" "$i"
  printf "Elapsed: %ds\n" "$elapsed"
  sleep 0.05
done