This blog documents my notes on creating Bash text bar charts. The key topics are:
- Create a simple horizontal bars (3 lines)
- Create dynamic bars (~25 lines for an array of data values)
- Add support for piped and command line data
- Include options for: help, resizing, titles
- Add colour and HTML support
A Horizontal Bar
To create a static bar an printf statements can be used. The seq {0..10} can be used to repeat an ASCII █ fill character 10 times.
$ printf 'LABEL: ' ; \
printf '█%.0s' {1..10} ; \
printf ' 10\n'
LABEL: ████████████████████ 10
Unfortunately the printf statement has some limitations on variable substitution. A simple workaround is to create a string and then eval it:
$ label="temp"; val=20;
$ bar="printf '█%.0s' {1..$val}" ;
$ printf '\n%-5s ' $label; eval $bar ; printf ' %d\n' $val
temp ████████████████████ 20
Coloured Bars
The tput setaf command can change the foreground, and tput setab is used for background colours. Colour codes are:
tput setab [1-7] # Set the background colour using ANSI escape
tput setaf [1-7] # Set the foreground colour using ANSI escape
Num Colour #define R G B
0 black COLOR_BLACK 0,0,0
1 red COLOR_RED 1,0,0
2 green COLOR_GREEN 0,1,0
3 yellow COLOR_YELLOW 1,1,0
4 blue COLOR_BLUE 0,0,1
5 magenta COLOR_MAGENTA 1,0,1
6 cyan COLOR_CYAN 0,1,1
7 white COLOR_WHITE 1,1,1
To reset colours back to the defaults use: tput sgr0
An example to print a red bar and a stack of bars:
$ printf '\nLABEL: ' ; \
tput setaf 1 ;\
printf '█%.0s' {1..10} ; \
printf ' 10\n'
LABEL: ██████████ 10
$ printf '\n 3 Stacked Bars: ' ; \ tput setaf 1 ;\ printf '█%.0s' {1..10} ; \ tput setaf 2 ;\ printf '█%.0s' {1..8} ; \ tput setaf 4 ;\ printf '█%.0s' {1..3} ; \ printf ' 10+8+3=21\n' 3 Stacked Bars: █████████████████████ 10+8+3=21
Dynamic Bars
The next step is to create a script that dynamically updates the bars. The tput clear command will clear the terminal screen keep the data and bars in the same location. The script below will dynamically show the CPU temperature, idle time and 2 random values with a 10 second update time.
#!/bin/bash
#
# cpu_bars.sh - Show new data every 10 seconds
#
while :; do
# Get data values
CPUtemp=$(sensors | grep CPU | awk '{print substr($2,2,4)}')
CPUidle=$(iostat | awk '{if (NR==4) print $6}')
Random1=$((1+ $RANDOM % 100))
Random2=$((1+ $RANDOM % 100))
labels=( CPUtemp CPUidle Random1 Random2)
values=( $CPUtemp $CPUidle $Random1 $Random2)
units=( degC % psi mm)
# Show a title
tput clear
printf " %10s " ""
tput setaf 7; tput smul;
printf "%s\n\n" "Show CPU Data ($(date +%T'))"
tput rmul;
# cycle thru data and show a label,
for index in "${!labels[@]}"
do
tput setaf $(expr $index + 1); # don't use 0 (black)
printf " %10s " "${labels[index]}"
eval "printf '█%.0s' {1..${values[index]%.*}}"
printf " %s %s\n\n" ${values[index]} ${units[index]}
done
sleep 10
done
This script is run by: bash cpu_bars.sh .Typical output is below.

Bars in HTML
The ANSI colours are not supported in HTML, so instead HTML tags and style properties can be used.
It is important to use <pre> tags for Bash text output. Code to create two static bars in HTML would be:
$ (printf "<h1>A Bar from Bash</h1>\n"
printf "<pre><span style='font-size:24px;color:red'}>\n"
printf 'LABEL1: ' ; printf '█%.0s' {1..10} ; printf ' 10\n'
printf "</pre></span>\n") > bar1.htm
$ cat bar1.htm
<h1>A Bar from Bash</h1>
<pre><span style='font-size:24px;color:red'}>
LABEL1: ██████████ 10
</pre></span>
The script cpu_webbars.sh creates HTML output for an array of calculated values:
#!/bin/bash
#
# cpu_webbars.sh - Show bars in HTML
#
# Get data values
CPUtemp=$(sensors | grep CPU | awk '{print substr($2,2,4)}')
CPUidle=$(iostat | awk '{if (NR==4) print $6}')
Random1=$((1+ $RANDOM % 100))
Random2=$((1+ $RANDOM % 100))
labels=( CPUtemp CPUidle Random1 Random2)
values=( $CPUtemp $CPUidle $Random1 $Random2)
units=( degC % psi mm)
colors=(red blue green magenta)
# Show a title
printf "<h1><center>Show CPU Data ($(date '+%T'))</center></h1>\n"
# cycle thru data and show a label,
for index in "${!labels[@]}"
do
printf "<pre><span style='font-size:18px;color: ${colors[index]} '}>\n"
printf " %10s " "${labels[index]}"
eval "printf '█%.0s' {1..${values[index]%.*}}"
printf " %s %s\n\n" ${values[index]} ${units[index]}
printf "</pre></span>\n"
done
This script can be run and outputted to an file: bash cpu_webbars.sh > test.htm

Once the basic HTML output is working its possible to add headers and footers to make a more complete page:
header.htm > test.htm ; \
cat cpu_webbars.sh >> test.htm ; \
cat footer >> test.htm
Piping Data to Bars
A script (hbar0.sh) will read the piped data and then create an array (data) of labels and values. The data array is cycled through and labels and bars are shown with a randomized colour:
#!/bin/bash
# hbar0.sh - Read in piped data and plot bars
# format: label,value;label2,value2; and plot bars
#
input=$(< /dev/stdin) ; # read piped data
# remove new lines in files, and check for ";" after data pair
input=$(echo $input | tr -d '\n')
IFS=';' read -r -a data <<< $input
printf "\n"
for element in "${data[@]}"pete@lubuntu:~/Writing/Blog/text_bars
do
# make at array of each data element
IFS=',' read -r -a datapt <<< $element
# add a random color
tput setaf $((1+ $RANDOM % 7))
# print the label, bar and value
printf " %10s " "${datapt##*[0]}"
bar="printf '█%.0s' {1..${datapt[1]}}"
eval $bar
printf " %s\n\n" ${datapt[1]}
tput rmso ; # exit color mode
done
The script can be tested with piped data:
$ echo "temp,33;pressure,44" | bash hbar0.sh temp █████████████████████████████████ 33 pressure ████████████████████████████████████████████ 44
A data file can also be passed in using the cat command:
$ cat data0.txt temp,44; humidity,33; pressure,15; wind spd,33; wave ht,3; $ cat data0.txt | bash hbar0.sh temp ████████████████████████████████████████████ 44 humidity █████████████████████████████████ 33 pressure ███████████████ 15 wind spd █████████████████████████████████ 33 wave ht ███ 3
Removing ANSI Colors
Terminal applications use ANSI color codes which unfortunately is not support on Web pages.
ANSI color codes can be removed from files and strings by:
# Strip out ANSI color codes:
cat infile | sed 's/\x1b\[[0-9;]*m//g' > outfile
$ echo "temp,33;pressure,44" | bash hbar0.sh > hbar0.txt $ cat hbar0.txt temp █████████████████████████████████ 33 pressure ████████████████████████████████████████████ 44 $ cat hbar0.txt | sed 's/\x1b\[[0-9;]*m//g' temp █████████████████████████████████ 33 pressure ████████████████████████████████████████████ 44
A Final App
With the basics in place I was able to create an app that would support scaling, titles, units, custom colour and web output:
$ ./hbars
usage: hbars [data] [option]
-h --help print this usage and exit
-c --color set color to all bars (default 7=white)
(0-grey,1-red,2=green,3=yellow,4=blue,5=magenta,6=cyan,7=white)
-p --pretty add different colors to bars (-c overrides)
-t --title top title
-w --width max width of bar (default 50)
-W --Web make output HTML formatted
-f --fontsize fontsize for web output (default 24)
examples:
echo 'temp,33,C;pressure,14,psi' | ./hbars -t Weather -p -w 40
./hbars -t Weather -p -w 40 'temp,33;pressure,14'
cat data.txt | ./hbars -W -f 24 -t 'Raspi Data' > data.htm
The code:
#!/bin/bash
#
# hbars.sh - show some text bars
# pass data as: label1,value1,unit1,color1; label2,value2,unit2,colour2; ....
#
width=50
title=""
color=7
pretty=False
web=False
font=24
usage() {
echo "usage: hbars [data] [option] "
echo " -h --help print this usage and exit"
echo " -c --color set color to all bars (default 7=white)"
echo " (0-grey,1-red,2=green,3=yellow,4=blue,5=magenta,6=cyan,7=white)"
echo " -p --pretty add different colors to bars (-c overrides)"
echo " -t --title top title"
echo " -w --width max width of bar (default 50)"
echo " -W --Web make output HTML formatted"
echo " -f --fontsize fontsize for web output (default 24)"
echo ""
echo " examples:"
echo " echo 'temp,33,C;pressure,14,psi' | ./hbars -t Weather -p -w 40 "
echo " ./hbars -t Weather -p -w 40 'temp,33;pressure,14' "
echo " cat data.txt | ./hbars -W -f 24 -t 'Raspi Data' > data.htm"
echo ""
exit 0
}
# Show help usage if no pipe data and no cmd line data
if [ -t 0 ] && [ $# -eq 0 ] ; then
usage
fi
# Check for command line options
while getopts "hpc:t:w:Wf:" arg; do
case "$arg" in
h) usage ;;
c) color=$OPTARG ;;
p) pretty=True; icolor=0 ;;
t) title=$OPTARG ;;
w) width=$OPTARG ;;
W) web=True;;
f) font=$OPTARG ;;
esac
done
#------------------------------------------------
# Setup formatting for text, Web and color
# -----------------------------------------------
if [[ ${color} != 7 && ${pretty} = True ]]; then
pretty=False
fi
colidx=0
setcolors() {
if [ $web = True ]; then
colors=(gray red green yellow blue magenta cyan white)
titlebold="echo '<h1>'"
titlereset="echo '</h1>'"
#color_set='echo "<span style=font-size:$(font)px >" '
#color_set="printf '<span style=\"font-size:$(font)px;color:${colors[colidx]}\" >'"
color_set="printf '<pre><span style=\"color:${colors[colidx]} ; font-size:${font}px \" >'"
color_rs="echo '</span></pre>'"
else
colors=(0 1 2 3 4 5 6 7 )
titlebold="tput bold; tput smul"
titlereset="tput rmul; tput rmso"
color_set="tput setaf ${colors[colidx]}"
color_rs="tput rmso"
fi
}
setcolors
#----------------------------------------------
# Get data, check if stdlin, file, if not assume string
#----------------------------------------------
if [ -p /dev/stdin ]; then
lastarg=$(< /dev/stdin)
else
lastarg=$(echo "${@: -1}")
if test -f "$lastarg"; then
lastarg=$(<$lastarg)
fi
fi
# Cleanup the input data
lastarg=$(echo $lastarg | sed 's/\n/;/g; s/ / /g; s/\t//g; s/;;/;/g')
IFS=';' read -r -a array <<< $lastarg
# ensure that there is some data
if [[ ${array} == 0 ]]; then
echo "No data found"
exit 0
fi
echo "input:$lastarg"
#exit 0
#------------------------------------
# Get max value and max label length
#------------------------------------
maxval=0
maxlbl=10
#echo "array:${array[@]}"
for element in "${array[@]}"
do
IFS=',' read -r -a datapt <<< $element
if (( $(echo "$maxval < ${datapt[1]}" |bc -l) )); then
maxval=${datapt[1]}
fi
if (( $(echo "$maxlbl < ${#datapt[0]}" |bc -l) )); then
maxlbl=${#datapt[0]}
fi
done
#---------------------------------
# Print Title - use bold/underline
#---------------------------------
if [[ ! -z $title ]]; then
printf "\n %${maxlbl}s " " "
eval $titlebold
printf "%s" "${title}" ; printf "\n\n"
eval $titlereset
fi
#------------------------------------
# Cycle thru data and build bar chart
#------------------------------------
for element in "${array[@]}"
do
# check data values
IFS=',' read -r -a datapt <<< $element
# check for empty records
if [ ${#datapt[0]} = 0 ]; then
break
fi
label=${datapt[0]}
if [[ ${label} != "-t*" ]]; then
val=${datapt[1]}
sval=$(bc <<< "$width * $val / $maxval")
# add color, use 4th item if available
if [[ ${#datapt[@]} > 3 && $pretty = False ]]; then
icolor=${datapt[3]}
fi
if [[ $pretty = True ]] ; then
let colidx++
if [ $colidx -gt 7 ]; then
let colidx=1
fi
elif [[ ${#datapt[@]} > 3 ]]; then
colidx=${datapt[3]}
else
colidx=$color
fi
setcolors
eval $color_set
printf " %${maxlbl}s " "$label"
bar="printf '█%.0s' {1..$sval}"
eval $bar;
# add value and units if available
units=""
if [[ ${#datapt[@]} > 2 ]]; then
units=${datapt[2]}
fi
printf " %d %s\n\n" $val "$units"
eval $color_rs
fi
done
SQL Output to Bars
With the base code I was able to start doing some more complicated actions, like piping SQL SELECT output. Below is an example from Sqlite3. The first step is to format the SQL output to: label,value;
pete@lubuntu:$ sqlite3 $HOME/dbs/someuser.db "select fname,age from users limit 4" Brooke|18 Leah|18 Pete|100 Fred|77 pete@lubuntu:$ sqlite3 $HOME/dbs/someuser.db "select fname,age from users limit 4" \ > | tr '|' ',' | tr '\n' ';' Brooke,18;Leah,18;Pete,100;Fred,77;
pete@lubuntu:$ sqlite3 $HOME/dbs/someuser.db "select fname,age from users limit 4" \ > | tr '|' ',' | tr '\n' ';' | ./hbars -t "Sqlite3 Users" -p Sqlite3 Users Brooke █████████ 18 Leah █████████ 18 Pete ██████████████████████████████████████████████████ 100 Fred ██████████████████████████████████████ 77
Web Page with Bars
Support was added for fixed chart width, engineering units and custom line colours. HTML <center> tags were used on the title.
$ cat data.txt
temp,44,degC,1;
humidity,33,%,4;
air pressure,88,mm Hg,5;
rain/precipation,6,mm,6;
$ cat data.txt | ./hbars -W -f 24 -w 50 -t '<center>Raspi Data</center>' > data.htm

Final Comments
Horizontal bars are relatives easy to create in Bash. Unfortunately showing vertical bars and Line charts will require a different approach