Skip to content

Instantly share code, notes, and snippets.

@brianclements
Last active November 13, 2023 10:48
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brianclements/5f97039fc25b03be76f17d13b1a05a03 to your computer and use it in GitHub Desktop.
Save brianclements/5f97039fc25b03be76f17d13b1a05a03 to your computer and use it in GitHub Desktop.
Quick and Dirty Bash Script Template
#!/bin/bash
##----------------
export _script_name=$(basename -a "$0")
export _script_short_desc="
A script for doing things."
export _script_mod_date='0000.00.00-00:00'
export _script_version='0.0.1'
export _script_requirements='none'
export _script_bash_debug=false
##----------------
shopt -s extglob ## Needed for arg parsing
## Internal config
export _script_subcommands_args=()
export _script_min_args=1
export _script_max_args=1
## Script Config
export working_dir=${working_dir:-"$PWD"}
# ----------------------------------------------------------------------------------------------
## Script
do_work() {
echo "doing work in ${working_dir}"
}
## Args
_parse_args() {
local _args=("$@")
while (( "${#_args}" )); do
case "$1" in
-@([a-z|A-Z]*))
# Switches
_parse_switches "$1"
;;
--*)
# Options
_parse_options "$1"
;;
*)
# Subcommands args stored as an array for later. For more
# complicated scripts, sometimes we need to act on the
# switches/options or source configuration first before we run
# the subcommands.
_script_subcommands_args=("$@")
break
;;
esac
shift
_args=("$@")
done
shopt -u extglob
# Check for minimum number of arguments/subcommands beyond switches and options here.
if [[ ${#_script_subcommands_args[@]} < $_script_min_args ]]; then
echo "Syntax error: a minimum of $_script_min_args arguments required."
echo ""
_usage_short
exit 1
fi
if [[ ${#_script_subcommands_args[@]} > $_script_max_args ]]; then
echo "Syntax error: only $_script_max_args argument allowed."
echo ""
_usage_short
exit 1
fi
}
_parse_switches() {
local _switches="$1"
_switches="${_switches:1}"
while (( "${#_switches}" )); do
# Handle actions for individual switches here
case "${_switches:0:1}" in
h)
_usage_short
exit 0
;;
v)
_version
exit 0
;;
*)
echo "Error: \"${_switches:0:1}\" not a valid switch"
echo ""
_usage_short
exit 1
;;
esac
_switches="${_switches:1}"
done
}
_parse_options() {
local _option=( $( IFS='='; echo ${1} ) )
local _option_keyword=${_option[0]:2}
# Get value of option if there is any
local _option_value=${_option[@]:1}
# Handle actions for individual options here
case "${_option_keyword}" in
bash-debug)
_script_bash_debug=true
;;
help)
_usage
exit 0
;;
version)
_version
exit 0
;;
*)
echo "Error: \"${_option_keyword}\" not a valid option"
echo ""
_usage_short
exit 1
;;
esac
}
_parse_subcommands() {
local _args=("$@")
while (( "${#_args}" )); do
case "$1" in
work)
echo "Args passed to work command: ${_args[@]:1}"
break
;;
*)
echo "Error: "$1" not a valid subcommand"
_usage
exit 1
;;
esac
shift
_args=("$@")
done
}
## Dialogs
_usage_cli=$(cat <<EOF
Usage: ${_script_name} [<switches>...] [<option keyword>=<value>...] <subcommand> <subcommand args>
${_script_short_desc}
EOF
)
_usage_args=$(cat <<EOF
Switches/Options:
--bash-debug Show bash debug info for *most* of the non-template part
of the script. It starts after the arguments are parsed.
-h|--help Short|Full help
-v|--version Version
Subcommands
work Werk it
EOF
)
_usage_short() {
cat <<EOF
${_usage_cli}
${_usage_args}
Run '${_script_name} --help' for the full help documentation.
EOF
}
_usage() {
cat <<EOF
${_usage_cli}
Switches can be combined, or used individually, in any order. Options must
follow the format --option="something here" and not have spaces in between
the option keyword and it's value.
By default:
- Any arguments that aren't switches or options are treated as erroneous, but
they could potentially be main arguments subcommands.
- The script processes all switches and all options, but stops only if
"exit" is declared in any of the 'case' statements. This is the current
behavior but can be easily changed.
${_usage_args}
Requirements:
${_script_requirements}
Version:
${_script_name} version: ${_script_version}
Last modifed on: ${_script_mod_date}
EOF
}
_version() {
echo "${_script_name} version: ${_script_version}"
echo "Last modifed on: ${_script_mod_date}"
}
## Sequence
_main() {
_parse_args "$@"
# I realize this only effects the non-template part of the script; this is usually fine.
# If you really want to debug the entire thing, then move `set -x` above `_parse_args "$@"`
if $_script_bash_debug; then
set -x
else
set -eo pipefail
fi
_parse_subcommands "${_script_subcommands_args[@]}"
# Default behavior
do_work
}
## Runtime
_main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment