Created
September 17, 2024 11:20
-
-
Save M1lan/1454743f94f455a0aeff692c29a71ea0 to your computer and use it in GitHub Desktop.
Bash template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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