Short Bash script to show Bar Graphs of Disk Usage

I ran out of space on the hard drive of a Linux computer, and didn't even know I was running low. So I've been thinking about how to display disk-free information in a useful way. My initial thought was to display warnings in my Bash Prompt: I may do that later. I was also thinking of displaying a bar chart in the prompt. Probably not: takes up too much space. But that led to me writing this (explanation follows):

#!/usr/bin/env bash
# filename: dft

#fillCharacter="|"
fillCharacter="$(echo -e "\e(0a\e(B")"

declare -A mppcent
declare -A mpsize

while read -r mountpoint
do
    # the weird "+ 0" math operation is to turn a String into an Integer ...
    mppcent["${mountpoint}"]=$(( $( df --output=pcent "${mountpoint}" | tail -n +2 | tr -d '%' ) + 0 ))
    mpsize["${mountpoint}"]="$( df --output=size -h "${mountpoint}" | tail -n +2 | tr -d ' ' )"
done < <( lsblk --output MOUNTPOINT --noheadings | grep -v '^$' | grep -v '[SWAP]' ; mount | grep " cifs " | awk '{ print $3 }' )

# $COLUMNS isn't available in scripts, so:
columns="$(tput cols)"

for mp in "${!mppcent[@]}"
do
    echo "${mp}: ${mpsize["${mp}"]}, ${mppcent["${mp}"]}%"
    fillcols=$(( columns * ${mppcent["${mp}"]} / 100 ))
    i=1
    while [ ${i} -le ${fillcols} ]
    do
        echo -n "${fillCharacter}"
        (( i++ ))
    done
    echo
done

That's 22 lines of code to produce an output that looks like this:

$ dft
/home/giles/share_pib: 1.4T, 39%
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
/: 23G, 67%
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
/home/giles/share_pi44fin1: 424G, 59%
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
/home: 421G, 77%
▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

There are a couple of things I plan to improve about this, but it's interesting at this point because it's simple yet effective.

Teardown

This is written on a Fedora box, and should work on any Linux (also tested on Debian). I seriously doubt it'll work on Mac, because of the number of options I'm using (options are often very different for the command line utilities). One thing I know for certain won't work on Mac, and may not work on Linux depending on your font, is the first active line of code:

fillCharacter="$(echo -e "\e(0a\e(B")"

This bizarre bit of work is a specialty character, a half-filled box (ie. looks gray in most cases). If this causes problems, switch to the other fillCharacter= line, which is just a pipe character.

declare -A name creates an associative array. We create two, one for Mount Point Per Cent and one for Mount Point Size (and once again curse Bash for not supporting multi-dimensional arrays, but hey, at least we have associative arrays - we didn't even have those in Bash 3 ...).

With the while read ... loop, look first at the input line attached to the end:

done < <( lsblk --output MOUNTPOINT --noheadings | grep -v '^$' | grep -v '[SWAP]' ; mount | grep " cifs " | awk '{ print $3 }' )

That's a lot to unpack. Normally you'd use done < $file-object to feed a while read loop, but here we're using the output of a process. And that process is:

lsblk --output MOUNTPOINT --noheadings | grep -v '^$' | grep -v '[SWAP]' ; mount | grep " cifs " | awk '{ print $3 }'

lsblk produces a list of block devices on your system. We use options to only output the MOUNTPOINT without the usual lsblk headings. Then a couple grep -v ... invocations are used to eliminate empty lines and any swap partitions. But on my system, I also wanted to catch some Samba shares, so we also include the output of mount, looking only for CIFS mounts and then print the third field, which is the mount point. Looking for CIFS filesystems on a system without them has no ill effect, but you can remove the semicolon and everything after it if you want (but do remember to close the parentheses enclosing the command).

Having read a mount point into the ${mountpoint} variable, we use the loop to populate the two arrays with information about the mount points, specifically the percent full and the full size of the partition. We use output options to df to show only the details we want ... but df always provides a header line so we chop that off with tail -n +2 which gets us line two and onward.

With all the data in hand, we create a for loop to go through our mount points and play back the data. The only trick here is calculating the width of the screen in columns, and then doing the math to figure out how many columns to fill to represent that percentage. And that's it!

Possible Improvements

  • write the text over top of the bar chart: this would halve the amount of space used, but would be tricky for various reasons and I'm actually liking the current layout
  • make the bar chart red if percentage use is over 90% (this is easy)
  • turn this into a tiny widget for a prompt so that it shows a single character block for each mount point, with colours green/yellow/red, switching at 70% and 90% full - kind of a stoplight arrangement (done)