From 7301dc38f1c69481480c235120ceb3221e5c5e07 Mon Sep 17 00:00:00 2001 From: William Melody Date: Wed, 8 Apr 2020 17:44:06 -0700 Subject: [PATCH] Improve option parsing and command detection. --- hosts | 209 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 115 insertions(+), 94 deletions(-) diff --git a/hosts b/hosts index 13a29ae..807c5f4 100755 --- a/hosts +++ b/hosts @@ -138,99 +138,6 @@ die() { _die echo "${@}" } -############################################################################### -# Options -# -# NOTE: The `getops` builtin command only parses short options and BSD `getopt` -# does not support long arguments (GNU `getopt` does), so the most portable -# and clear way to parse options is often to just use a `while` loop. -# -# For a pure bash `getopt` function, try pure-getopt: -# https://github.com/agriffis/pure-getopt -# -# More info: -# http://wiki.bash-hackers.org/scripting/posparams -# http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html -# http://stackoverflow.com/a/14203146 -# http://stackoverflow.com/a/7948533 -# https://stackoverflow.com/a/12026302 -# https://stackoverflow.com/a/402410 -############################################################################### - -# Get raw options for any commands that expect them. -_RAW_OPTIONS="${*:-}" - -# Parse Options ############################################################### - -# Initialize $_COMMAND_ARGV array -# -# This array contains all of the arguments that get passed along to each -# command. This is essentially the same as the program arguments, minus those -# that have been filtered out in the program option parsing loop. This array -# is initialized with $0, which is the program's name. -_COMMAND_ARGV=("${0}") -# Initialize $_CMD and `$_USE_DEBUG`, which can continue to be blank depending -# on what the program needs. -_CMD="" -_USE_DEBUG=0 -_AUTO_SUDO=0 - -while [[ ${#} -gt 0 ]] -do - __opt="${1}" - - shift - - case "${__opt}" in - -h|--help) - _CMD="help" - ;; - --version) - _CMD="version" - ;; - --debug) - _USE_DEBUG=1 - ;; - --auto-sudo|--sudo) - _AUTO_SUDO=1 - ;; - *) - # The first non-option argument is assumed to be the command name. - # All subsequent arguments are added to $_COMMAND_ARGV. - if [[ -n "${_CMD:-}" ]] - then - _COMMAND_ARGV+=("${__opt}") - else - _CMD="${__opt}" - fi - ;; - esac -done - -# Set $_COMMAND_PARAMETERS to $_COMMAND_ARGV, minus the initial element, $0. This -# provides an array that is equivalent to $* and $@ within each command -# function, though the array is zero-indexed, which could lead to confusion. -# -# Use `unset` to remove the first element rather than slicing (e.g., -# `_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]:1}")`) because under bash 3.2 the -# resulting slice is treated as a quoted string and doesn't easily get coaxed -# into a new array. -_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]}") -unset "_COMMAND_PARAMETERS[0]" - -_debug printf \ - "\${_CMD}: %s\\n" \ - "${_CMD}" -_debug printf \ - "\${_RAW_OPTIONS} (one per line):\\n%s\\n" \ - "${_RAW_OPTIONS}" -_debug printf \ - "\${_COMMAND_ARGV[*]}: %s\\n" \ - "${_COMMAND_ARGV[*]}" -_debug printf \ - "\${_COMMAND_PARAMETERS[*]:-}: %s\\n" \ - "${_COMMAND_PARAMETERS[*]:-}" - ############################################################################### # Environment ############################################################################### @@ -331,7 +238,7 @@ _main() { if _contains "${_CMD}" "${_DEFINED_COMMANDS[*]:-}" then # Pass all comment arguments to the program except for the first ($0). - ${_CMD} "${_COMMAND_PARAMETERS[@]:-}" + "${_CMD}" "${_COMMAND_PARAMETERS[@]:-}" else _die printf "Unknown command: %s\\n" "${_CMD}" fi @@ -1478,6 +1385,120 @@ unblock() { done } +############################################################################### +# Options +# +# NOTE: The `getops` builtin command only parses short options and BSD `getopt` +# does not support long arguments (GNU `getopt` does), so the most portable +# and clear way to parse options is often to just use a `while` loop. +# +# For a pure bash `getopt` function, try pure-getopt: +# https://github.com/agriffis/pure-getopt +# +# More info: +# http://wiki.bash-hackers.org/scripting/posparams +# http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html +# http://stackoverflow.com/a/14203146 +# http://stackoverflow.com/a/7948533 +# https://stackoverflow.com/a/12026302 +# https://stackoverflow.com/a/402410 +############################################################################### + +# Get raw options for any commands that expect them. +_RAW_OPTIONS="${*:-}" + +# Parse Options ############################################################### + +# Initialize $_COMMAND_ARGV array +# +# This array contains all of the arguments that get passed along to each +# command. This is essentially the same as the program arguments, minus those +# that have been filtered out in the program option parsing loop. This array +# is initialized with $0, which is the program's name. +_COMMAND_ARGV=("${0}") +# Initialize $_CMD and `$_USE_DEBUG`, which can continue to be blank depending +# on what the program needs. +_CMD="" +_USE_DEBUG=0 +_AUTO_SUDO=0 + +_SUBCOMMANDS=( + add + backups + block + commands + disable + disabled + edit + enable + enabled + file + help + list + remove + search + show + unblock + version +) +_SUBCOMMANDS_PATTERN="$(_join '|' "${_SUBCOMMANDS[@]}")" + +while [[ ${#} -gt 0 ]] +do + __opt="${1}" + + shift + + case "${__opt}" in + -h|--help) + _CMD="help" + ;; + --version) + _CMD="version" + ;; + --debug) + _USE_DEBUG=1 + ;; + --auto-sudo|--sudo) + _AUTO_SUDO=1 + ;; + *) + # The first non-option argument is assumed to be the command name. + # All subsequent arguments are added to $_COMMAND_ARGV. + if [[ -z "${_CMD:-}" ]] && [[ "${__opt:-}" =~ ${_SUBCOMMANDS_PATTERN} ]] + then + _CMD="${__opt}" + else + _COMMAND_ARGV+=("${__opt}") + fi + ;; + esac +done + +# Set $_COMMAND_PARAMETERS to $_COMMAND_ARGV, minus the initial element, $0. This +# provides an array that is equivalent to $* and $@ within each command +# function, though the array is zero-indexed, which could lead to confusion. +# +# Use `unset` to remove the first element rather than slicing (e.g., +# `_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]:1}")`) because under bash 3.2 the +# resulting slice is treated as a quoted string and doesn't easily get coaxed +# into a new array. +_COMMAND_PARAMETERS=("${_COMMAND_ARGV[@]}") +unset "_COMMAND_PARAMETERS[0]" + +_debug printf \ + "\${_CMD}: %s\\n" \ + "${_CMD}" +_debug printf \ + "\${_RAW_OPTIONS} (one per line):\\n%s\\n" \ + "${_RAW_OPTIONS}" +_debug printf \ + "\${_COMMAND_ARGV[*]}: %s\\n" \ + "${_COMMAND_ARGV[*]}" +_debug printf \ + "\${_COMMAND_PARAMETERS[*]:-}: %s\\n" \ + "${_COMMAND_PARAMETERS[*]:-}" + ############################################################################### # Run Program ###############################################################################