aliquote.org

Quick Bash script templates

September 12, 2022

Over the years, I wrote a lot of little helper Bash scripts to automate things on macOS or Ubuntu. Most of the times it was simply a matter of putting a series of variables and commands, some echo and the like, after a shebang line. In the end, this was messy but it worked as intended.

Here are two templates that may simplify the process of writing small shell scripts. They both share the same principles: the use of “subcommand” and the display of a short help message in case argument $1 is empty (since there seems to be so much a lack of convention that I never remember if we need to pass help, --help or -help after program name to get some help).

The first template is minimalist. There’s no error checking, and it is assumed that instructions can be grouped logically into independent main options like this:

#!/usr/bin/env bash

case "$1" in
fizz)
	echo -e "\033[0;32m[+] Process first option\033[0m"
	;;
buzz)
	echo -e "\033[0;32m[+] Do some banana\033[0m"
	;;
help)
	echo -e "Usage: $0 {fizz|buzz}"
	;;
*)
	echo -e "Usage: $0 {fizz|buzz}"
	;;
esac

As can be seen, this is pretty basic stuff, and everything is handled via the first argument to the shell script (recall that $0 is the program name, $1 its first argument, etc.). Of course, feel free to add all sort of guards (set -e, set -u, set -o pipefail) at the top of your script. My default Vim template for shell scripts include most of them, and I delete unnecessary ones depending on my needs.

[2022-10-27]
See also Shell Script Best Practices to learn a lot more tips and tricks. It also features a nice Bash template.

The second template is a little more involved as it exemplifies the use of proper subcommands, as in Hugo or Git for instance. The following script is a real script that I use to update a changelog with Git revisions in specific Git repositories on my hard drive (my website and all Git projects that are in the same directory, $HOME/cwd). Here’s the gist of it:

#!/usr/bin/env bash

whos=$(basename "$0")
tmpdir="$HOME"/tmp

_help() {
    echo "Usage: $whos <subcommand> [options]"
    echo "Subcommands:"
    echo "    update   Update log entries"
    echo "    show     Show log entries"
    echo ""
    echo "For help with each subcommand run:"
    echo "$whos <subcommand> -h|--help"
    echo ""
}

_update() {
    cd "$HOME"/Sites/aliquote || return
    git log --author=chl --pretty=format:"* %as %an <%ae> - [${PWD/*\//}/%h] %s" >"$tmpdir"/home-chl-sites-aliquote.log

    for d in $(find "$HOME"/cwd -maxdepth 1 -type d ! -name "cwd"); do
        WD=${d##*/}
        if [[ -d "$HOME/cwd/$WD/.git" ]]; then
            cd "$HOME"/cwd/"$WD" || return
            git log --author=chl --pretty=format:"* %as %an <%ae> - [${WD}/%h] %s" >"$tmpdir"/home-chl-cwd-"$WD".log
        fi
    done

    cat "$tmpdir"/home-chl-*.log >"$tmpdir"/logger
    sort -k2 -r "$tmpdir"/logger >"$HOME"/Documents/logs/changelog
    rm "$tmpdir"/home-chl-*.log "$tmpdir"/logger
}

_show() {
    less "$HOME"/Documents/logs/changelog | rg "[0-9]+-[0-9]+-[0-9]+"
}

subcommand=$1
case $subcommand in
"" | "-h" | "--help")
    _help
    ;;
*)
    shift
    _${subcommand} "$@"
    if [ $? = 127 ]; then
        echo "Error: '$subcommand' is not a known subcommand." >&2
        echo "       Run '$whos --help' for a list of known subcommands." >&2
        exit 1
    fi
    ;;
esac

In this case, each subcommand is implemented as its own function, and they are processed at the command line using a simple variable interpolation. If you need more flexibility to handle your subcommand, I think this is better suited than the preceding sample script. I came across a similar approach in a Gist a while ago but I forgot to bookmark it and I can’t credit the author unfortunately.

♪ Gilad Hekselman • Flower

See Also

» How to get by without using a tiling WM » Usenet newsgroups with Neomutt » More Neomutt little hacks » Migrating to Systemd » Starship