Skip to content

Instantly share code, notes, and snippets.

@M1lan
Created September 17, 2024 11:20
Show Gist options
  • Save M1lan/1454743f94f455a0aeff692c29a71ea0 to your computer and use it in GitHub Desktop.
Save M1lan/1454743f94f455a0aeff692c29a71ea0 to your computer and use it in GitHub Desktop.
Bash template
#!/usr/bin/env bash
##
# Note: almost always, we want to inherit the encapsulating shell
# environment. If we don't, we can run scripts from within a clean
# shell environment like this: #!/usr/bin/env -iS -- bash
##
# Shellcheck configuration.
#
# shellcheck enable=avoid-nullary-conditions
# shellcheck enable=add-default-case,check-extra-masked-returns
# shellcheck enable=check-set-e-suppressed,require-double-brackets
# shellcheck enable=check-unassigned-uppercase,deprecate-which
#
#####################################################################
# production ready bash #
#####################################################################
##
# This is a simple template for production ready Bash scripts.
#
# It also includes a style-guide.
#
# In production, we want to know about all failure conditions and
# ensure they are handled correctly. Bash is not the best language
# for that and we need to know the subtle differences between exit
# codes and what is printed to stdout/stderr and what means sucess or
# failure in the specific case we execute scripted commands or
# external commands. Using `set -euo pipefail` doesn't guarantee
# anything. The idea is more that instead of keep going at all
# costs, we want to fail early.
#
#####################################################################
# style guide #
#####################################################################
##
# - Always use `shellcheck` or `shellharden` (or both)! The former
# prints information about warnings and errors and how to fix them,
# while the latter simply prints the already fixed script.
#
# - Column limit: 79 column (similar to PEP-8).
#
# - Semi-reliable exit codes: If anything in the script exits with a
# non-zero exit code, we want the whole script to stop immediately
# and also return with the same code. Never use `<command> || true`
# unless we really don't care about <command>. C.f. BASHFAQ 105.
#
# - Use leading underscores on internal variable and function names
# in order to avoid name collisions. For unintentionally global
# variables defined without `local`, such as those defined outside of
# a function or automatically through a `for` loop, prefix with
# double underscores.
#
# - Preferably use braces when referencing variables, `"Hi, my name
# is: ${NAME}."` instead of `"Hi, my name is $NAME."`. While quotes
# are required for variable references, braces are only required in
# some cases.
# Bad: `some_command $arg1 $arg2 $arg3`
# Bad: `some_command ${arg1} ${arg2} ${arg3}`
# Good: `some_command "${arg1}" "${arg2}" "${arg3}"`
# Good: `some_command "$arg1" "$arg2" "$arg3"`
#
# - Prefer `printf` over `echo`. For more information, see:
# http://unix.stackexchange.com/a/65819
#
# - Prefer `$_explicit_variable_name` over names like `$var`.
#
# - Use the `#!/usr/bin/env bash` shebang in order to run the
# preferred Bash version rather than hard-coding the executable
# path.
#####################################################################
# header #
#####################################################################
# exit immediately on err; useful for CI/CD.
set -e
# exit if referencing undefined var.
set -u
# return exit code of failed command in pipe, not last.
set -o pipefail
# filenames might have spaces
IFS=$'\n\t'
# This script's basename.
_SELF="$(basename "${0}")"
_PRINT_HELP=0
_USE_DEBUG=0
__DEBUG_COUNTER=0
_OPTION_X=0
_OPTION_VALUE=
# everything that comes after '--' is not an option flag, but usually
# the filename we run the script on or an action command.
_TARGET_FILE_OR_ACTION=
#####################################################################
# helpers #
#####################################################################
##
# _debug()
#
# Usage:
# _debug <command> <options>
#
# Description: Print debug info to stderr.
#
# Example: _debug printf "[DEBUG] Variable: %s\\n" "$0"
_debug() {
if ((${_USE_DEBUG:-0}))
then
__DEBUG_COUNTER=$((__DEBUG_COUNTER+1))
{
printf "――――――――――――――――――――――――――――――――――――――――――――――――――――――\\n"
printf "🐛 %s " "$__DEBUG_COUNTER >>"
"${@}"
printf "――――――――――――――――――――――――――――――――――――――――――――――――――――――\\n"
} 1>&2
fi
}
##
# _exit_1()
#
# Usage:
# _exit_1 <command>
#
# Description: Exit with status 1 after sending a message to stdout
# and stderr.
_exit_1() {
{
printf "%s " "$(tput setaf 1 || true)!$(tput sgr0 || true)"
"${@}"
} 1>&2
exit 1
}
##
# _warn()
#
# Usage:
# _warn <command>
#
# Description: Print warning about a non-critical error to stdout and
# stderr, but don't exit.
_print_warning() {
{
printf "%s " "$(tput setaf 1 || true)!$(tput sgr0 || true)"
"${@}"
} 1>&2
}
##
# _print_help()
#
# Usage:
# _print_help
#
# Description: Print the program help information.
_print_help() {
cat <<EOF
Put your helpful text here.
Usage:
${_SELF} [--options] [<arguments>] [--] [target, action, or file]
${_SELF} -h | --help
Options:
-h, --help Display this help information.
-d, --debug Debug mode.
-x, --option-x A simple option 'x'.
-o, --option-with-value Some option, followed by a required value.
EOF
}
##
# __get_option_value()
#
# Usage:
# __get_option_value <option> <value>
#
# Description: For the given option, return the value or exit 1 if
# value is blank or appears to be another option.
__get_option_value() {
local __arg="${1:-}"
local __val="${2:-}"
if [[ -n "${__val:-}" ]] && [[ ! "${__val:-}" =~ ^- ]]
then
printf "%s\\n" "$__val"
else
_exit_1 printf "%s requires a valid argument.\\n" "$__arg"
fi
}
# parse options
while ((${#}))
do
__arg="${1:-}"
__val="${2:-}"
case "$__arg" in
-h|--help)
_PRINT_HELP=1
;;
-d|--debug)
_USE_DEBUG=1
;;
-x|--option-x)
_OPTION_X=1
;;
-o|--option-with-value)
_OPTION_VALUE="$(__get_option_value "$__arg" "${__val:-}")"
shift
;;
-- )
_TARGET_FILE_OR_ACTION="$(IFS=$' '; printf "%s" "${*:2}")"
break
;;
-*)
_exit_1 printf "Unexpected option: %s\\n" "$__arg"
;;
*)
_TARGET_FILE_OR_ACTION="$(IFS=$' '; printf "%s" "${*:1}")"
break
;;
esac
shift
done
#####################################################################
# script starts here #
#####################################################################
_example() {
_debug printf "this is a debug message...\\n"
## handle options
if ((_OPTION_X))
then
printf "Received -x. Doing nothing.\\n"
_debug printf "the --option-x flag is set.\\n"
else
_debug printf "the --option-x flag is NOT set.\\n"
fi
if [[ -n "$_OPTION_VALUE" ]]
then
printf "Option value: %s\\n" "$_OPTION_VALUE"
fi
if [[ -n "$_TARGET_FILE_OR_ACTION" ]]
then
printf "Operating on file / selected action: %s\\n" "$_TARGET_FILE_OR_ACTION"
fi
## do stuff here!
printf "Bash is here: %s\\n" "$(command -v bash || true)"
printf "Script ran successfully.\\n"
}
# _main()
#
# Usage:
# _main [<options>] [<arguments>]
#
# Description:
# Entry point.
_main() {
if ((_PRINT_HELP))
then
_print_help
else
_example "$@"
fi
}
_main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment