Mangle long-option names to allow dashes

Fixes problem where long options with dashes, like `--no-color` were
 broken. This was due to the fact that bash variable names must match
 `[_a-zA-Z][_0-9a-zA-Z]*` and the usage parsing and option handling was
 trying to create variables with dashes in their names. Short of
 employing Bash4 associative arrays, "name mangling" seemed like the
 best solution to this problem.

Solution: map dashes to underscores when creating bash variables in the
script that correspond to long option flags. The downside of this is
that `--no_color` and `--no-color` will collide, but users expecting to
use options that are identical except one has an underscore and the
other has a dash deserve the ensuing confusion.
This commit is contained in:
Izaak Beekman 2016-02-19 23:25:09 -05:00
parent db690268ea
commit a86d8d82b1
No known key found for this signature in database
GPG Key ID: CB21118C92A64702

21
main.sh
View File

@ -56,6 +56,8 @@ read -r -d '' usage <<-'EOF' || true # exits non-zero when EOF encountered
-v Enable verbose mode, print script as it is executed -v Enable verbose mode, print script as it is executed
-d --debug Enables debug mode -d --debug Enables debug mode
-h --help This page -h --help This page
-n --no-color Disable color output
-1 --one Do just one thing
EOF EOF
# Set magic variables for current file and its directory. # Set magic variables for current file and its directory.
@ -120,14 +122,15 @@ trap cleanup_before_exit EXIT
# Translate usage string -> getopts arguments, and set $arg_<flag> defaults # Translate usage string -> getopts arguments, and set $arg_<flag> defaults
while read line; do while read line; do
# fetch single character version of option sting # fetch single character version of option string
opt="$(echo "${line}" |awk '{print $1}' |sed -e 's#^-##')" opt="$(echo "${line}" |awk '{print $1}' |sed -e 's#^-##')"
# fetch long version if present # fetch long version if present
long_opt="$(echo "${line}" |awk '/\-\-/ {print $2}' |sed -e 's#^--##')" long_opt="$(echo "${line}" |awk '/\-\-/ {print $2}' |sed -e 's#^--##')"
long_opt_mangled="$(sed 's#-#_#g' <<< $long_opt)"
# map long name back to short name # map long name back to short name
varname="short_opt_${long_opt}" varname="short_opt_${long_opt_mangled}"
eval "${varname}=\"${opt}\"" eval "${varname}=\"${opt}\""
# check if option takes an argument # check if option takes an argument
@ -157,6 +160,9 @@ opts="${opts}-:"
# Reset in case getopts has been used previously in the shell. # Reset in case getopts has been used previously in the shell.
OPTIND=1 OPTIND=1
# start parsing command line
set +o nounset # unexpected arguments will cause unbound variables
# to be dereferenced
# Overwrite $arg_<flag> defaults with the actual CLI options # Overwrite $arg_<flag> defaults with the actual CLI options
while getopts "${opts}" opt; do while getopts "${opts}" opt; do
[ "${opt}" = "?" ] && help "Invalid use of script: ${@} " [ "${opt}" = "?" ] && help "Invalid use of script: ${@} "
@ -166,13 +172,15 @@ while getopts "${opts}" opt; do
if [[ "${OPTARG}" =~ .*=.* ]]; then if [[ "${OPTARG}" =~ .*=.* ]]; then
# --key=value format # --key=value format
long=${OPTARG/=*/} long=${OPTARG/=*/}
long_mangled="$(sed 's#-#_#g' <<< $long)"
# Set opt to the short option corresponding to the long option # Set opt to the short option corresponding to the long option
eval "opt=\"\${short_opt_${long}}\"" eval "opt=\"\${short_opt_${long_mangled}}\""
OPTARG=${OPTARG#*=} OPTARG=${OPTARG#*=}
else else
# --key value format # --key value format
# Map long name to short version of option # Map long name to short version of option
eval "opt=\"\${short_opt_${OPTARG}}\"" long_mangled="$(sed 's#-#_#g' <<< $OPTARG)"
eval "opt=\"\${short_opt_${long_mangled}}\""
# Only assign OPTARG if option takes an argument # Only assign OPTARG if option takes an argument
eval "OPTARG=\"\${@:OPTIND:\${has_arg_${opt}}}\"" eval "OPTARG=\"\${@:OPTIND:\${has_arg_${opt}}}\""
# shift over the argument if argument is expected # shift over the argument if argument is expected
@ -183,14 +191,15 @@ while getopts "${opts}" opt; do
varname="arg_${opt:0:1}" varname="arg_${opt:0:1}"
default="${!varname}" default="${!varname}"
value="${OPTARG:-}" value="${OPTARG}"
if [ -z "${OPTARG:-}" ] && [ "${default}" = "0" ]; then if [ -z "${OPTARG}" ] && [ "${default}" = "0" ]; then
value="1" value="1"
fi fi
eval "${varname}=\"${value}\"" eval "${varname}=\"${value}\""
debug "cli arg ${varname} = ($default) -> ${!varname}" debug "cli arg ${varname} = ($default) -> ${!varname}"
done done
set -o nounset # no more unbound variable references expected
shift $((OPTIND-1)) shift $((OPTIND-1))