Skip to content

Instantly share code, notes, and snippets.

@markusfisch
Last active May 9, 2023 20:52
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markusfisch/4612424 to your computer and use it in GitHub Desktop.
Save markusfisch/4612424 to your computer and use it in GitHub Desktop.
Generate a bar chart by reading value/label pairs from stdin

Bar charts from stdin

Put this shell script somewhere in your path and run it by feeding it value/label pairs like this:

$ bars
10 one
20 two
30 three
^D

And you'll get something like this:

10   one ################
20   two ################################
30 three #################################################

Value comes first because labels are optional. If you're data is the other way round, just use awk before bars:

$ awk '{ print $2 $1 }' < your_data | bars

Examples

Get a bar chart of word frequencies in a text file:

$ tr -sc 'A-Za-z' '\012' < textfile | sort | uniq -c | sort -nr | bars

Get a bar chart of file sizes:

$ du -hk * | bars

Show a chart of total lines per file:

$ wc -l * | bars

Use another character for the bars:

$ BARCHAR='*' bars

Pad values to have fixed width:

$ VMAX=8 bars
#!/usr/bin/env bash
# Generate a bar chart by reading value/label pairs from stdin
bars() {
local VALUES=() LABELS=()
local EOL=${EOL:-$'\n'} MAX=${BARMAX:-0} VMAX=${VMAX:-0} LMAX=${LMAX:-0}
local N=0 V L LN
while read -r V L
do
(( V > MAX )) && MAX=$V
VALUES[$N]=$V
LABELS[$N]=$L
(( ++N ))
LN=${#L}
(( LN > LMAX )) && LMAX=$LN
LN=${#V}
(( LN > VMAX )) && VMAX=$LN
done
local COUNT=$N COLUMNS=${COLUMNS:-$(tput cols)}
local HC=$(( COLUMNS/2 ))
(( LMAX > HC )) && LMAX=$(( HC ))
local BARCHAR=${BARCHAR:-#}
local BMAX=$(( COLUMNS-LMAX-VMAX-1 ))
local BAR='' LSPACE=''
if (( LMAX > 0 ))
then
(( --BMAX ))
LSPACE=' '
fi
while (( ${#BAR} < BMAX ))
do
BAR=$BAR${BAR:-$BARCHAR}
done
local F=$(( (MAX<<16)/BMAX ))
(( F < 1 )) && F=1
for (( N = 0; N < COUNT; ++N ))
do
L=${LABELS[$N]}
(( ${#L} > LMAX )) && L='...'${L:$(( ${#L}-LMAX+3 ))}
printf "%${VMAX}d %${LMAX}s%s%s$EOL" \
"${VALUES[$N]}" \
"$L" \
"$LSPACE" \
"${BAR:0:$(( (${VALUES[$N]}<<16)/F ))}"
done
}
if [ "${BASH_SOURCE[0]}" == "$0" ]
then
bars
fi
#!/usr/bin/env bash
test_single_line() {
for (( N = 0; N <= RANGE; ++N ))
do
echo "$N" | BARMAX=$RANGE ./bars
done
}
test_multi_line() {
for (( N = 0; N <= RANGE; ++N ))
do
echo "$N"
done | BARMAX=$RANGE ./bars
}
test_multi_line_alt_char() {
BARCHAR='=' test_multi_line
}
test_custom_width() {
COLUMNS=60 test_multi_line
}
test_progress_bar() {
for (( MAX=100, N = 0; N <= MAX; N += 5 ))
do
echo "$N" '%' | VMAX=3 BARMAX=$MAX EOL=$'\r' ./bars
sleep 1
done
echo
}
test_long_labels() {
du -hk "$(cd "${0%/*}" && echo "${PWD%/*}")"/* | ./bars
}
test_with_total() {
wc -l "${0%/*}"/* | ./bars
}
test_word_frequency() {
tr -sc 'A-Za-z' '\012' < "$0" | sort | uniq -c | sort -nr | ./bars
}
readonly RANGE=${RANGE:-20}
if (( $# > 0 ))
then
for TEST in "$@"
do
$TEST
done
else
grep '^test_' "$0" | while read -r TEST
do
TEST=${TEST%%(*}
$TEST
done
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment