bash histogram

I would like to generate a streamable histgram that runs in bash. Given an input stream of integers (from stdin or a file), I would like to transform each integer to that respective number of “#” up to the length of the terminal window; in other words, 5 would become “#####”, and so on.

You can get the maximum number of columns in your current terminal using the following command,

twarnock@laptop: :) tput cols
143

The first thing we’ll want to do is create a string of “####” that is exactly as long as the max number of columns. I.e.,

COLS=$(tput cols);
MAX_HIST=`eval printf '\#%.0s' {1..$COLS}; echo;`

We can use the following syntax to print a substring of MAX_HIST to any given length (up to its maximum length).

twarnock@laptop: :) echo ${MAX_HIST:0:5}
#####
twarnock@laptop: :) echo ${MAX_HIST:0:2}
##
twarnock@laptop: :) echo ${MAX_HIST:0:15}
###############

We can then put this into a simple shell script, in this case printHIST.sh, as follows,

#! /bin/bash
COLS=$(tput cols);
MAX_HIST=`eval printf '\#%.0s' {1..$COLS}; echo;`

while read datain
do
  if [ -n "$datain" ]; then
    echo -n ${MAX_HIST:0:$datain}
    if [ $datain -gt $COLS ]; then
      printf "\r$datain\n"
    else
      printf "\n"
    fi
  fi
done < "${1:-/dev/stdin}"

This script will also print any number on top of any line that is larger than the maximum number of columns in the terminal window.

As is, the script will transform an input file into a crude histogram, but I’ve also used it as a visual ping monitor as follows (note the use of unbuffer),

twarnock@cosmos:~ :) ping $remote_host | unbuffer -p awk -F'[ =]' '{ print int($10) }' | unbuffer -p printHIST.sh
######
#####
########
######
##
####
#################
###
#####
#######