Last active
February 26, 2026 14:25
-
-
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 hidden or 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)) |
Author
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
;;
...
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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(whichgetoptshas converted to$OPTARG), e.g.-cin place of--config.Working around that is only a little ugly: Add
"--$opt"after eachset -- "$@"in the secondcaseof theforloop excluding the*clause. Before that final clause, add a new-* ) set -- "$@" "--$opt" "$opt" ;;clause. Add-:to thegetoptsoptstring. Add- ) param="$OPTARG" ;;to thegetoptsloop'scasestanza, and then refer to$paraminstead of-$OPTARG.