Bash Hacking on Exercism.io

During Christmas break I treated myself to the idle diversion of honing my bash skills.

/logos/gnu_bash-official.svg

I must have blasted through about 6000 cookies as I solved a dozen or so bash programming challenges this week (at a frightful clip). Learning new languages by solving Exercism.io challenges has been my casual vice for a while now. Since I already use the bourne again shell incessantly in my work, I have always wanted to dig a bit deeper and really learn the nuts and bolts.

Bash is really weird. Maybe that’s because the shell is somewhat akin to a Ship of Theseus, representing the culmination of decades worth of design innovations stretching all the way back to the storied UNIX days of Bell Labs in the 1970s, where such giants as Ken Thompson and Dennis Ritchie created the seminal C Programming Language and the iconic UNIX operating system. There is a beautiful MC-Esheresque quality to the closely intertwined development of C and UNIX; two amazing tools that were created in mutual benefit of each other. The first implements the second, which facilitates development of the first.

/DrawingHands.jpg

The harsh upshot is that all those years of evolution has had the unfortunate side-effect of creating many situations where arbitrary design decisions become more significant than they ought to be, on account of the implications to program portability. I often also felt unduly challenged to understand the minute differences between variant uses of syntax, and experienced great pining for Python because of some spectacularly bizarre and counter-intuitive behaviors in Bash, like the character-set collation issue

Watch out! - 10 is sometimes less than 2

$ #!/usr/bin/env bash
$ function print() { (( $1 )) && printf X || printf .; }
$ for n in 1 2 9 10 20 'a'; do
$ (( $n < 2 )); print $?
$ [[ $n < 2 ]]; print $?
$ (( $n > 2 )); print $?
$ [[ $n > 2 ]]; print $?
$ printf "\t$n\n"
$ done
..XX    1
XXXX    2
XX..    9
X..X    10
XX..    20
.XX.    a

Watch out - case matching with “case”

$ #!/usr/bin/env bash
$ echo "   cc ^^ ,,"
$ 
$ for c in 'a' 'A' 'z' 'Z'; do
$ printf "$c  "
$ 
$ case $c in
$ [A-Z]) printf X ;;
$ [a-z]) printf . ;;
$ esac
$ case $c in
$ [a-z]) printf . ;;
$ [A-Z]) printf X ;;
$ esac
$ printf " "
$ 
$ case ${c^} in
$ [A-Z]) printf X ;;
$ [a-z]) printf . ;;
$ esac
$ case ${c^} in
$ [a-z]) printf . ;;
$ [A-Z]) printf X ;;
$ esac
$ printf " "
$ 
$ case ${c,} in
$ [A-Z]) printf X ;;
$ [a-z]) printf . ;;
$ esac
$ case ${c,} in
$ [a-z]) printf . ;;
$ [A-Z]) printf X ;;
$ esac
$ 
$ printf "\n"
$ done
   cc ^^ ,,
a  .. X. ..
A  X. X. ..
z  X. XX X.
Z  XX XX X.

printf derives from the C Programming Language

A fun thing about bash is that there are many delightfully peculiar ways to accomplish a task, and sketchy old-school hacks are often the most portable way to do it. One curious example is how printf (which is inherited from C programming) makes it possible to recall ordinal ASCII characters - but for some arcane reason, only by using octal!

$ printf "64 in Octal: $(printf "%o" 64)\n"
$ printf "ASCII 65 is: \\$(printf "%o" 65)\n"
64 in Octal: 100
ASCII 65 is: A