I’ve been doing a bit of work lately setting up a new machine for my job, and in the process making some scripts that can ease the process for later developers. The whole thing is written in Bash. Why might you find yourself wanting such a thing?
Downsides to this:
So, having said that, let’s talk about building a toolbelt, and then how to skip that.
So, we can build a cute little toolbelt like so (adapted from here):
#! /usr/bin/env bash
set -Ceuo pipefail
do_goose() {
echo "honk!"
}
do_echo() {
echo $@
}
subcommand=${1:-}
case $subcommand in
"" )
echo "Missing subcommand."
;;
"help" )
echo "help info would go here."
;;
*)
shift # munch subcommand
do_${subcommand} $@
;;
esac
And, after chmod +x
ing this command, you could put it through its paces and see:
crertel@attenborough:~$ ./goose.sh help
help info would go here.
crertel@attenborough:~$ ./goose.sh
Missing subcommand.
crertel@attenborough:~$ ./goose.sh honk
./goose.sh: line 23: do_honk: command not found
crertel@attenborough:~$ ./goose.sh goose
honk!
crertel@attenborough:~$ ./goose.sh echo honk honk honk
honk honk honk
Now, a couple of observations:
#! /usr/bin/env bash
instead of something else because it better respects the user’s path. This is not an unalloyed good, however. set -Ceuo pipefail
to make sure that: overwriting files without doing >!
isn’t allowed (noclobber via C
), that the script exits the first time it hits an error instead of exploding (errexit via e
), that using an unbound variable forces an exit and a warning (nounset via u
), and finally that if something breaks in a command pipeline we don’t keep executing everything downstream of it using its error message as stdin (pipe fail via -o pipefail
). I strongly suggest you use this in all your Bash scripts until you have a particular need not to. subcommand=${1:-}
to make sure that if there is no first positional argument/sub-command specified, we get an empty string. So, that was neat, but there’s a better (and more structured) way.
I was introduced to this by my old boss. It’s a framework called sub, originally from Basecamp but forked and tweaked and updated by various folks–I’ve linked to the one Zillow uses.
Sub is nice because it gives a clean structure to build your toolbelt:
libexec
folder named for the sub-command you want and prefixed by the toolbelt name and an underscore . If you were making a toolbelt called gripe
with a sub-command “loudly”, you’d put a file at gripe/libexec/gripe_loudly
, and it would be picked up by sub. Usage:
and Help:
comments as per the readme, and you magically get usage, summary of commands, and detailed help for users. It should take you maybe an afternoon to build your first couple of commands. Things that will probably trip you up include: refactoring common helper functions out into their own shell scripts, properly determining the path to the shell scripts so things don’t break when you run this from other directories, figuring out how to place files relative to wherever the toolbelt is installed, etc.
These are all tractable problems, but they will be a bit frustrating. Oh, also, if you want colons in your sub-commands like gripe auth:login
you can do it by putting it in the filename but know that that’s super. weird.
But, if you get this working, you can do neat things like:
All kinds of stuff, and you can just kinda load it down over time with things you find helpful. It’ll serve you well, and because it’s in naked bash, it won’t break when you put it onto a machine with weird programming environments. You don’t need Node! You don’t need Python! You don’t need Ruby!
Some things that I’ve found very useful or inspiring along these lines: