Shell script template

Last updated 2021-06-28

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.


Available directly at (shortened).


set -e  # Exit immediately if a command files

# Test whether these variables are set before set -u
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

    -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

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

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

# Collect arguments:
# Known flags are collected and acted on first, and any positional arguments
# are kept to the end
# -1 onething positional  # Call your script like this
# 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

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

# 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 )
            # 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
        -1 | --one-argument )
            # An option that takes 1 argument: consume 2 arguments from $@
            shift 2
        -2 | --two-arguments )
            # An option that takes 2 arguments: consume 3 arguments from $@
            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; }
            # 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"

# 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"

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