PercBar — Create a progress bar (Bash package)

Below you find the latest version of (1) the package's README and (2) its main source file.

For the git repository and issue tracker, see the project's page on sr.ht.

For more packages, see Software.


README.org

Overview

A Bash package to create a progress bar labeled with percentages.

Usage

percbar num [den len]

where:

  • num is the amount (numerator)
  • den is the total (denominator)
  • len is the bar length
  • num ≤ den

If no den is passed, it's assumed to be 100.
If no len is passed, bar length will be 50.

Examples

Here are a few examples.

Regular

1%

# Same as percbar 1 100
percbar 1
1%                      99%
█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100%

20%

# Same as percbar 20 100
percbar 20
   20%                      80%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100%

20 out of 60

percbar 20 60
       33%                      67%
█████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100%

20 out of 60, bar length 70

percbar 20 60 70
          33%                                67%
███████████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100%

7 out of 21, bar length 12

percbar 7 21 12
33%   67%
████░░░░░░░░ 100%

e ÷ π

percbar 2.718281828459045 \
        3.141592653589793
                    87%                      13%
███████████████████████████████████████████░░░░░░░ 100%

24.2 ÷ 42

percbar 24.2 42
             58%                      42%
█████████████████████████████░░░░░░░░░░░░░░░░░░░░░ 100%

Modified

Now what if you wanted to change features of the bar?

I did consider adding some flags for options (say, -n for no total, etc). But decided against it, because:

  1. Version 0.2.0 has less than 70 actual lines of code — which is good.
    I wanted to keep it just as short and simple.
  2. And modifying the output with a sed filter is simple and cheap.

So you can modify it yourself by defining one-line functions that look like these:

foo() { percbar "$@" | sed "something" ;}

Here are some examples:

20%, no % labels

foo() { percbar "$@" | sed 1d ;}
foo 20
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100%

20%, no total

foo() { percbar "$@" | sed "2 s/ .*//" ;}
foo 20
   20%                      80%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

20%, but just the bar

foo() { percbar "$@" | sed "1d; s/ .*//" ;}
foo 20
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

20%, just that label

foo() { percbar "$@" | sed "s/  *[^%]*%$//" ;}
foo 20
   20%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

20%, total shown as number

foo() { percbar "$@" | sed "2 s/100%/${2:-100}/" ;}
foo 20
foo 42 210
foo 8.4 42
   20%                      80%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 100
   20%                      80%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 210
   20%                      80%
██████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 42

(Replacing instead the top % labels with actual numbers is not as straightforward:
these could be of any width, so their positions would need to be recalculated for them to end up centered.)

Different block characters

foo() { percbar "$@" | sed "y/█░/●◌/" ;}
foo 24.2 42
             58%                      42%
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌ 100%

Different block characters, just complete %

foo() { percbar "$@" | sed "y/█░/●◌/; s/  *[^%]*%$//" ;}
foo 24.2 42
             58%
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌

Different block characters, just complete % and the total as number

foo() { percbar "$@" | sed "y/█░/●◌/; 2 s/100%/${2:-100}/; 1 s/  *[^%]*%$//" ;}
foo 24.2 42
             58%
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌◌ 42

Animation

Want to animate the bar?

Here's a demo you can hack on:

clear -x
for i in {0..17}; do
    printf "%s\r" "$(percbar "$i" 17 | sed 1d)"
    sleep 0.5
done; echo

Installation

See my page Software for the most up-to-date instructions on how to download and install my packages.

Dependencies: bc must be available in your system.

Contributing

See my page Software for information about how to contribute to my packages.

News

0.2.0

Release

License

This project follows the REUSE Specification (FAQ), which in turn is built upon SPDX.

Therefore, license and copyright information can be found in:

  • each file's comment header, or
  • an adjacent file of the same name with the additional extension .license, or
  • the .reuse/dep5 file

The full text of the licenses can be found in the LICENSES subdirectory.


percbar

Contents

#!/usr/bin/env bash

# PercBar --- Create a progress bar

# SPDX-FileCopyrightText: © flandrew <https://flandrew.srht.site/listful>
# SPDX-License-Identifier: GPL-3.0-or-later

#---------------------#
# Author:  flandrew   #
# Created: 2018-01-26 #
# Updated: 2025-10-31 #
#---------------------#
# Version: 0.2.0      #
#---------------------#

### Commentary
#
# For usage, run: percbar -h
#
#############################################################################

### Code

# Dependencies
require() { hash "$@" || exit 127 ;}
require bc

#############################################################################

usage() { cut -c5- <<EOU
    Usage:
      percbar num [den len]

    where:
    - num is the amount (numerator)
    - den is the total (denominator)
    - len is the bar length
    - num ≤ den

    If no den is passed, it's assumed to be 100.
    If no len is passed, bar length will be 50.
EOU
          exit "${1:-0}" ;}

#############################################################################

# Numbers must be composed of only digits and a maximum of one decimal point
isnumber() [[ -z "$(<<<"$1" sed 's/\.//; s/[0-9]//g')" ]]

# Round the number
round() { <<<"$1*$2/$3 + 0.5" bc -l | sed '/^\./ s/^/0/; s/\..*//' ;}

#############################################################################

main() {
    local pfull bfull bnorm ctpad outper fullchar num \
          pempt bempt ltpad rtpad outbar emptchar den n

    # Parse input
    [[ "$1" ]] && num="$1"   || usage 9
    [[ "$1" == "-h" ]]       && usage 0
    [[ "$2" ]] && den="$2"   || den=100
    [[ "$3" ]] && bnorm="$3" || bnorm=50
    for n in 1 2 3; do
        isnumber "${!n}" || { echo "Error: ${!n} is not a number" >&2
                              exit "$n" ;}
    done

    # Percentage numbers & Blocks in the bar
    ## completed, rounded
    pfull=$(round 100      "$num" "$den")
    bfull=$(round "$bnorm" "$num" "$den")
    ((pfull <= 100)) || { echo "Error: need 0% ≤ result ≤ 100%" >&2
                          exit 4 ;}
    ## differences
    ((pempt= 100   - pfull ,
      bempt= bnorm - bfull))

    # Percentages' positions
    ((_= bfull < 2   ? 0
      :  pfull < 10  ? bfull - 2
      :  pfull < 100 ? bfull - 3
      :                bnorm - 4 ,
      ltpad= _ / 2               ,
      _= bempt < 2   ? -2
      :  pfull < 10  ? bempt - 2
      :  pfull < 100 ? bempt - 3
      :                bnorm - 4 ,
      rtpad= _ / 2               ,
      _= pfull < 10  ? bfull - 2
      :  pfull < 100 ? bfull - 3
      :                bnorm - 4 ,
      ctpad= _ + rtpad - ltpad))

    # Produce the output
    outper=$(printf '%*s'   "$ltpad" | tr ' ' s
             printf '%s'    "$pfull"%
             printf '%*s'   "$ctpad" | tr ' ' s
             printf '%s\n'  "$pempt"%)

    outbar=$(printf '%*s'   "$bfull" | tr ' ' f
             printf '%*s\n' "$bempt" | tr ' ' e)

    fullchar="█"  # U+2588
    emptchar="░"  # U+2591

    sed "y/s/ /"     <<< "$outper"
    sed "y/fe/$fullchar$emptchar/
         s/$/ 100%/" <<< "$outbar"
}

#############################################################################

main "$@"
exit 0
📆 2025-11-02