-
-
Save adamhotep/895cebf290e95e613c006afbffef09d7 to your computer and use it in GitHub Desktop.
| # a refinement of https://stackoverflow.com/a/5255468/519360 | |
| # see also my non-translating version at https://stackoverflow.com/a/28466267/519360 | |
| # translate long options to short | |
| reset=true stopped="" | |
| for opt in "$@"; do | |
| if [ -n "$reset" ]; then | |
| unset reset | |
| set -- # reset the "$@" array so we can rebuild it | |
| fi | |
| case "$opt" in # --option=argument -> opt='--option' optarg='argument' | |
| --?*=* ) optarg="${opt#*=}" opt="${opt%%=*}" ;; | |
| * ) unset optarg ;; | |
| esac | |
| case "$stopped$opt" in | |
| -- ) stopped=true; set -- "$@" -- ;; | |
| --help ) set -- "$@" -h ;; | |
| --verbose ) set -- "$@" -v ;; | |
| --config ) set -- "$@" -c ${optarg+"$optarg"} ;; | |
| --long-only ) DEMO_LONG_ONLY_FLAG=true ;; | |
| # pass anything else through, including spaced arguments | |
| * ) set -- "$@" "$opt" ;; | |
| esac | |
| done | |
| # now we can process with getopt | |
| while getopts ":hvc:" opt; do | |
| case $opt in | |
| h ) usage ;; | |
| v ) VERBOSE=true ;; | |
| c ) source $OPTARG ;; | |
| \? ) usage ;; | |
| : ) | |
| echo "option -$OPTARG requires an argument" | |
| usage | |
| ;; | |
| esac | |
| done | |
| shift $((OPTIND-1)) |
Aside from needing a preprocessing loop, this approach has a flaw in that it loses the long option name; if you trigger that : clause (meaning you've forgotten an option's argument), the complaint uses $opt (which getopts has converted to $OPTARG), e.g. -c in place of --config.
Working around that is only a little ugly: Add "--$opt" after each set -- "$@" in the second case of the for loop excluding the * clause. Before that final clause, add a new -* ) set -- "$@" "--$opt" "$opt" ;; clause. Add -: to the getopts optstring. Add - ) param="$OPTARG" ;; to the getopts loop's case stanza, and then refer to $param instead of -$OPTARG.
If you pass a long-only option that requires an argument, the above code only works if you pass it like --option=arg. It does not work without the =. I got it to work like this but is there a better way:
reset=true stopped="" grab=
for option in "$@"; do
...
# If $grab is set then we have an option waiting for an argument
if [ -n "$grab" ]; then
optarg=$opt # $opt holds the needed argument
opt=$grab # recheck the previous option
unset grab # unset the flag
fi
case "$stopped$opt" in
--long-only)
# $optarg is normally only set if we passed --opt=arg. If we passed
# '--opt arg' without the '=' we need to grab the next value of $opt
# as our argument
#
# $optarg is set so we can process the option
if [ -n "$optarg" ]; then
echo "Got --long-only=$optarg"
# $optarg is not set so we set a flag to the name of this option so
# the next time through the loop we can match this block again
else
grab=--long-only
fi
;;
...
Today's edits add support for
--option=argumentwithout as much ugliness as previously anticipated. If you don't want that, remove the firstcasestanza and the${optarg:+"$optarg"}part of--config(though leaving them in is harmless).This code uses a some parameter substitutions. The first one,
${opt#*=}, takes the value of$optwithout the first=and the non-equals-sign characters that precede it (akas/^[^=]*=//). The second one,${opt%%=*}, pulls greedily from the end, removing the first=and everything that follows it (akas/=.*$//).The third subsitution,
${optarg+"$optarg"}, ensures we only add the argument when it was actually defined. If we used"$optarg"instead, we'd be adding an empty string as the argument and--config foo.confwould become-c '' foo.confwhich will runsource ''(resulting insh: 31: source: not found) andgetoptswill terminate given the standalonefoo.confeven if more options follow.This is a little tricky. If we used
${optarg:+"$optarg"}instead, that extra colon changes the logic given an empty assignment. Consider: