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, and it follows my opinions of best shell script practice.
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