Shell script template

I use this template as a starting place for shell scripts.

It shows a couple of different ways to process arguments, and a nice help message.

As with all formulae, I may update this from time to time.

Template

Available directly at template.sh (shortened).

#!/bin/sh

set -e  # Exit immediately if a command files

# Test whether these variables are set before set -u
TEMPLATEDBG=${TEMPLATEDBG:-}
set -u  # Treat unset variables as errors

cmdname=`basename "$0"`

usage() {
    cat <<ENDUSAGE
Usage: $cmdname [-h] [-d] [-1 arg1] [-2 arg1 arg2] [-e] [POSITIONALARGS...]
A template for new POSIX shell scripts

ARGUMENTS
    -h | --help: Print help and exit
    -d | --debug: show debug messages
    -1 | --one-argument: consume the next input argument
    -2 | --two-arguments: consume the next two input arguments
    -e | --exit-with-error: exit with an error
    POSITIONALARGS: printed at the end

VARIABLES
    \$TEMPLATEDBG: If set, print debugging info whether or not -d was passed
ENDUSAGE
}

dbgecho() {
    if test "$TEMPLATEDBG"; then
        echo "$@"
    fi
}

# Collect arguments:
# Known flags are collected and acted on first, and any positional arguments
# are kept to the end
#   script.sh -1 onething positional  # Call your script like this
#   script.sh positional -1 onething  # This will NOT work!
# In the first case, 'positional' will be the only positional argument
# In the second case, 'positional', '-1', and 'onething' are all treated as
# positional arguments
oneargval=default
twoarg1=default
twoarg2=default

if test $# -eq 0; then
    # If we do not provide an argument, show help and exit with error
    usage
    exit 1
fi

# Our default obfuscation scheme is just to cat the input we receive
obfuscate() { cat; }
test_obfsc_string="I think I have made this script too complicated"

while test $# -gt 0; do
    case "$1" in
        -h | --help )
            usage
            # If we pass the help argument intentionally, exit without error
            exit 0
            ;;
        -d | --debug )
            # A bare option: consume only 1 argument from $@
            set -x # Show each line before executing
            shift
            TEMPLATEDBG=1
            ;;
        -1 | --one-argument )
            # An option that takes 1 argument: consume 2 arguments from $@
            oneargval=$2
            shift 2
            ;;
        -2 | --two-arguments )
            # An option that takes 2 arguments: consume 3 arguments from $@
            twoarg1=$2
            twoarg2=$3
            shift 3
            ;;
        -e | --exit-with-error )
            # When exiting for an error, use a value greater than zero
            exit 1
            ;;
        -o | --obfuscate )
            # Consume no other arguments, but enable rot13 obfuscation
            obfuscate() { tr a-zA-Z n-za-mN-ZA-M; }
            shift
            ;;
        *)
            # You should process each positional argument as it comes in here.
            # It is tempting to collect them into a variable like $posargs, and
            # that can work if you understand $IFS. However, it's ugly and non-
            # obvious; I think simply dealing with each positional argument as
            # it is processed makes the most sense.
            echo "Positional argument: $1"
            shift
            ;;
    esac
done

# An example of an alternative way to process arguments
# This way is useful for scripts that don't need option flag arguments
altargproc() {
    # NOTE: when processing arguments this way, make sure to quote "$@". Unless
    #       it is quoted, sh will split unquoted arguments on spaces.
    #       For instance, say the script is called like this:
    #         altargproc "one" "two fuckin"
    #       If $@ is unquoted, the for loop will iterate THREE times, with the
    #       $arg variable as "one", "two", and "fuckin".
    #       However, if "$@" is quoted, the loop will iterate TWO times, with the
    #       $arg variable as "one" and "two fuckin".
    echo "Alternative argument processing example:"
    for arg in "$@"; do
        echo "- $arg banana"
    done
}

echo ""
if test $TEMPLATEDBG; then echo "Debug mode enabled"; else echo "Debug mode disabled"; fi
dbgecho "Debug messages only get printed if debug mode is enabled"
echo "oneargval = $oneargval, twoarg1 = $twoarg1, twoarg2 = $twoarg2"

echo ""
altargproc "one" "two fuckin" "three"

echo ""
echo "Sample string to obfuscate:"
echo "$test_obfsc_string"
echo "After obfuscation:"

# Just a dumb example to show how you can use functions defined this way in the pipeline
echo "$test_obfsc_string" |
    obfuscate |
    tee /dev/null