Skip to content

Instantly share code, notes, and snippets.

@jefferys
Created November 14, 2017 21:43
Show Gist options
  • Save jefferys/5bd5a00da65024fcf51c1c5291a38c74 to your computer and use it in GitHub Desktop.
Save jefferys/5bd5a00da65024fcf51c1c5291a38c74 to your computer and use it in GitHub Desktop.
A template for a bash function parsing CLI options and arguments into global variables, including short, long, repeated, counted, and --.
###############################################################################
# DESC: Parses arguments with the bash built-in getopts, but allows for
# long options too. This only works when each long option has a short option,
# although short options need not have long ones. Designed to create global
# variables named opt_<option> containing the parsed command line options, and
# an array variable called opt_args with any additional non-option arguments.
# This is all hard coded, but simple to modify for local use. See the ###
# sections for what needs to be changed to create your own option variable set.
# Supports bundled options and the use of "--" to signal end of options.
# Does not support negated options. You can always just declare "myFlag" and
# "noMyFlag" as separate options. Nor does it support options that may or may
# not have values. If an option takes a value it must have one provided when
# used on the command line (possibly ""). Options not given of the command line
# have no value (are "unset"). Defaults can be set before calling. However, for
# counted options, repeated options, and the arguments, if set before calling
# the default count or array of values will added to by any command line
# options or arguments. To get a default that is "replaced" for these, they
# can be set after calling, where unset options can be checked for (although
# it may be hard to tell if any were set explicitly to "").
# RET: Returns 1 if called more than once per sub-shell. Uses a global
# variable _get_opts_called_ to implement this.
# PARAM: $ARGS The command line arguments array to parse, with $0.
# VAR: $DEBUG can be set (to anything, except "") before calling to echo
# the processed list of long to short options actually parsed by getopts, if
# something is not working.
# VAR: * One global variable is created for each option.
# USAGE: get_my_opts "$@"
# # get_my_opts is a function whose code you copy and paste into
# # your shell script, and then modify the parts that correspond
# # to your options in the ### START ... ### END blocks. This is a
# # code *template*.
###############################################################################
function get_my_opts {
# Parameters copied to new array, preserving elements with spaces.
# Not required but defensive. BasNote "${@}" automatically drops $0.
args=( "${@}" )
# Prevent calling this function twice in the same sub shell.
if [ ! -z ${_get_opts_called_} ]
then
echo "Can't call get_opts twice!"
return 1
fi
_get_opts_called_=true
#==========================================================================
# What does bash do with that?
# declare -a <aVar>
# Creates an array variable named aVar. Will be local if used inside a
# function
# "${arrVar[@]}"
# The entire array arrVar as a list of comma delimited elements. Preserves
# space-containing elements.
# ${#arrVar[@]}
# The count of elements in the array arrVar.
# case <var> in
# Selects a ;; terminated <val>) block based on matching the string <val>
# against the string <var> The ) delimts the end of the value to match.
# Quoted values are matched exactly, patterns can include * as a glob, e.g.
# "text"* matches strings starting with "text", like 'texture', but also
# "text".
# ( "${aArrVar[@]}" "${bArrVar[@]}" )
# Concatenate two arrays, preserving elements with spaces, due to quotes.
# Creates an array even when used with non-array elements. Empty elements
# are NOT included.
# $(( ))
# Required to do math, returns the value of the wrapped expression.
# "${arrVar[@]:<start>:<count>}"
# "${arrVar[@]:<start>}"
# Array slicing in bash, Returns <count> elements from the array arrVar
# begining with <start>. Quotes required to preserve elements with spaces.
# If :<count> is ommitted, retuns the everything, starting with <start>
#==========================================================================
# Pre-process command line, converting long options to short ones.
# This iterates over a copy of the arg array, $args, translating or copying
# each element as needed into a new array, rawOptions.
#
# If an element is a known long option, this translates it to the hard-
# coded short option and adds it to the rawOptions array. If it is an
# unknown long option, it calls usage with an error message. If it
# is '--' (end-of-options flag), it and the rest of the arguments are
# added to the array in one step without translating any arguments that
# might look like long options. Otherwise, the element is a value, short
# option, or argument, so it is just added as is.
# Declare the array to build the translated arguments into.
declare -a rawOptions
pos=0
for elem in "${args[@]}" # Array as list
do
case "${elem}" in
#------------------------------------------------------------------
# Every long option must be matched to a short option. Add one line
# like this for each long option.
#------------------------------------------------------------------
### START Long options:
"--myFlag") rawOptions[$pos]="-f" ;;
"--myInteger") rawOptions[$pos]="-i" ;;
"--myString") rawOptions[$pos]="-s" ;;
"--myBoolean") rawOptions[$pos]="-b" ;;
"--myCounted") rawOptions[$pos]="-c" ;;
"--myRepeated") rawOptions[$pos]="-r" ;;
### END Long options:
"--") rawOptions=( "${rawOptions[@]}" "${args[@]:${pos}}" )
break
;;
"--"*) echo "Unknown long option ${elem}"
return 1
;;
*) rawOptions[$pos]="${elem}" ;;
esac
pos=$(( $pos + 1 ))
done
# DEBUG can be set before calling to see the processed list of long
# options, if something is not working.
if [ ! -z ${DEBUG} ]
then
echo "Option list after long to short processing:"
echo "${rawOptions[@]}"
fi
# Now we have a command line with only short options, values, arguments,
# or '--', we can process with the getopts built-in
#--------------------------------------------------------------------------
# Have to set the option descriptor for getopts, e.g. "fi:s:b:cr::".
# Every option that takes a value must be folowed with a ":", and the
# last character shoud be an (additional) ":".
#--------------------------------------------------------------------------
### START Option Descriptor
while getopts "fi:s:b:cr::" option "${rawOptions[@]}"
### END Option Descriptor
do
case "$option" in
#------------------------------------------------------------------
# Every short option must assign to a variable here. Variables
# generally should be named for the associated long options,
# opt_<longOption>. Examples here show the code for each of
# 6 kinds of options. Counted and repeated options are recursive
# and must use the option name in the code too.
# Add one line matching the kind of option for each short option.
#------------------------------------------------------------------
### START: Option variables
"f") opt_myFlag=true ;;
"i") opt_myInteger=${OPTARG} ;;
"s") opt_myString="${OPTARG}" ;;
"b") opt_myBoolean=${OPTARG} ;;
"c") opt_myCounted=$(( ${opt_myCounted:-0} + 1 )) ;;
"r") opt_myRepeated=( "${opt_myRepeated[@]}" "${OPTARG}" ) ;;
### END: Option variables
\?) echo "Unknown option -${OPTARG}"
return 1
;;
\:) echo "Option -${OPTARG} requires a vaule."
return 1
;;
esac
done
#--------------------------------------------------------------------------
# You can change the name of the arguments variable used here. Its
# recursive, so change in the code too.
#--------------------------------------------------------------------------
### START: Arguments variable
opt_args=( "${opt_args[@]}" "${rawOptions[@]:(( ${OPTIND} - 1 ))}" )
### STOP: Arguments variable
}
########
# DEMO #
########
# This is not part of the function, just a demo of how it is used.
# Only one can be run, due to the recursive use of globally defined values and
# how getopts works within a single shell/subshell.
# Call to get_my_opts, the customized option defining function. Instead of
# passing it "$@", an explicit list of options that looks like the command line
# is used.
# get_my_opts -f -i 123 -s "bob bob" -b false -c -c -c -r "one" -r "and a two" "Arg 1" "Arg 2"
#
# echo
# echo " AFTER:"
# echo "opt_flag: ${opt_myFlag}"
# echo "opt_integer: ${opt_myInteger}"
# echo "opt_string: ${opt_myString}"
# echo "opt_boolean: ${opt_myBoolean}"
# echo "opt_counted: ${opt_myCounted}"
# echo "opt_repeated size: ${#opt_myRepeated[@]}"
# echo "opt_repeated 1: ${opt_myRepeated[0]}"
# echo "opt_repeated 2: ${opt_myRepeated[1]}"
# echo "opt_args size: ${#opt_args[@]}"
# echo "opt_args 1: ${opt_args[0]}"
# echo "opt_args 2: ${opt_args[1]}"
# Call to get_opts, with -- defining the end of options, allowing for option-like parameters
get_my_opts -b false -ccc -r one -r "and a two" -fi 123 -s "bob bob" -- --anArg -Arg2 "an arg with spaces"
echo
echo " AFTER:"
echo "opt_flag: ${opt_myFlag}"
echo "opt_integer: ${opt_myInteger}"
echo "opt_string: ${opt_myString}"
echo "opt_boolean: ${opt_myBoolean}"
echo "opt_counted: ${opt_myCounted}"
echo "opt_repeated size: ${#opt_myRepeated[@]}"
echo "opt_repeated 1: ${opt_myRepeated[0]}"
echo "opt_repeated 2: ${opt_myRepeated[1]}"
echo "opt_args size: ${#opt_args[@]}"
echo "opt_args 1: ${opt_args[0]}"
echo "opt_args 2: ${opt_args[1]}"
echo "opt_args 2: ${opt_args[2]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment