Created
November 14, 2017 21:43
-
-
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 --.
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
############################################################################### | |
# 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