-
-
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)) |
Today's edits add support for --option=argument
without as much ugliness as previously anticipated. If you don't want that, remove the first case
stanza 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 $opt
without the first =
and the non-equals-sign characters that precede it (aka s/^[^=]*=//
). The second one, ${opt%%=*}
, pulls greedily from the end, removing the first =
and everything that follows it (aka s/=.*$//
).
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.conf
would become -c '' foo.conf
which will run source ''
(resulting in sh: 31: source: not found
) and getopts
will terminate given the standalone foo.conf
even 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:
unset test # $test is not defined
set -- a ${test+"$test"}
echo $# # there is ONE parameter
set -- a ${test:+"$test"}
echo $# # there is ONE parameter
test="" # $test is defined but empty
set -- a ${test+"$test"}
echo $# # there are TWO parameters
set -- a ${test:+"$test"}
echo $# # there is ONE parameter
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
.
This is a refinement of the suboptimal code at https://stackoverflow.com/a/5255468/519360
See also my own code to support long options in POSIX shell at https://stackoverflow.com/a/28466267/519360
That second link supports arguments in the form
--foo=bar
while the code here supports arguments in the form--foo bar
. It wouldn't be hard to adapt this code to support both while my preferred method (which only has one loop) would need to get rather ugly to do it.