Last active
November 29, 2023 23:22
-
-
Save adamhotep/895cebf290e95e613c006afbffef09d7 to your computer and use it in GitHub Desktop.
POSIX shell: support long options by converting them to short options
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
# 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
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Today's edits add support for
--option=argument
without as much ugliness as previously anticipated. If you don't want that, remove the firstcase
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 (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.conf
would become-c '' foo.conf
which will runsource ''
(resulting insh: 31: source: not found
) andgetopts
will terminate given the standalonefoo.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: