Bash #2 - Functional Command Relay

Posted April 26, 2021 ‐ 2 min read

In this post I introduce a nice use for functions in bash.

Previous post: #1.

In software projects, would often appear a 'scripts' directory with various scripting utilities. The inexperienced bash scriptwriter would usually not use functions at all and instead litter up the directory with many execution entry points such as:

build.sh
run.sh
deploy.sh

If we would like to clean this up and combines these functions to a single file, say command.sh, how one would do so?

A nice way to accomplish is using the following:

#!/bin/bash

# ... common code here ...

build() {
    # Original content and logic of bash.sh
}

run() {
    # Original content and logic of run.sh
}

deploy() {
    # Original content and logic of deploy.sh
}

"$@"

(for clarification regarding "$@", see the previous post regarding execution relay)

Here is our example execution:

$ ./command.sh build <params>
<output of the original build logic>

Functions in bash behave like mini-scripts in the same manner that original scripts would behave, and similar to 'real' executable programs that are already reachable from $PATH. It is very convenient that bash functions are inter-changeable with regular programs. For instance, they can be used in shell pipe components and in sub-shells and under $(..).

I call the method above the Functional Command Relay pattern, as it allows execution of each function with the parameters to these functions relayed as-is. It can even be used recursively to represent a command tree in script command line parsing.

A natural extension to this method is choosing how strict we are with the command itself, which is the first parameter to the main script. For example, we can replace the last "$@" line with the following:

case $1 in
    build|run|deploy) "$@" ;;
    '') print_help; exit -1 ;;
    *) echo "Invalid command '$1'" ; exit -1 ;;
esac

Share this post
Follow author