Yet another option parser (generator)
An elegant option parser for shell scripts (full support for all POSIX shells) https://github.com/ko1nksm/getoptions (Update: v3.3.0 released on 2021-05-02)
getoptions is a new option parser (generator) written in POSIX-compliant shell script and released in august 2020. It is for those who want to support the POSIX / GNU style option syntax in your shell scripts.
The supported syntaxes are -a, +a, -abc, -vvv, -p VALUE, -pVALUE, --flag, --no-flag, --with-flag, --without-flag, --param VALUE, --param=VALUE, --option[=VALUE], --no-option --.
It supports subcommands, validation, abbreviated options, and automatic help generation. And works with all POSIX shells (dash 0.5.4+, bash 2.03+, ksh88+, mksh R28+, zsh 3.1.9+, yash 2.29+, busybox ash 1.1.3+, etc).
#!/bin/sh VERSION="0.1" parser_definition() { setup REST help:usage -- "Usage: example.sh [options]... [arguments]..." '' msg -- 'Options:' flag FLAG -f --flag -- "takes no arguments" param PARAM -p --param -- "takes one argument" option OPTION -o --option on:"default" -- "takes one optional argument" disp :usage -h --help disp VERSION --version } eval "$(getoptions parser_definition) exit 1" echo "FLAG: $FLAG, PARAM: $PARAM, OPTION: $OPTION" printf '%s\n' "$@" # rest arguments
It's parses the following arguments:
example.sh -f --flag -p VALUE --param VALUE -o --option -oVALUE --option=VALUE 1 2 3
And automatic help generation.
$ example.sh --help Usage: example.sh [options]... [arguments]... Options: -f, --flag takes no arguments -p, --param PARAM takes one argument -o, --option[=OPTION] takes one optional argument -h, --help --version
It is also an option parser generator, generates the following simple option parsing code. If you use the generated code, you won't need getoptions. Achieve true portability and zero dependency.
FLAG='' PARAM='' OPTION='' REST='' getoptions_parse() { OPTIND=$(($#+1)) while OPTARG= && [ $# -gt 0 ]; do case $1 in --?*=*) OPTARG=$1; shift eval 'set -- "${OPTARG%%\=*}" "${OPTARG#*\=}"' ${1+'"$@"'} ;; --no-*|--without-*) unset OPTARG ;; -[po]?*) OPTARG=$1; shift eval 'set -- "${OPTARG%"${OPTARG#??}"}" "${OPTARG#??}"' ${1+'"$@"'} ;; -[fh]?*) OPTARG=$1; shift eval 'set -- "${OPTARG%"${OPTARG#??}"}" -"${OPTARG#??}"' ${1+'"$@"'} OPTARG= ;; esac case $1 in '-f'|'--flag') [ "${OPTARG:-}" ] && OPTARG=${OPTARG#*\=} && set "noarg" "$1" && break eval '[ ${OPTARG+x} ] &&:' && OPTARG='1' || OPTARG='' FLAG="$OPTARG" ;; '-p'|'--param') [ $# -le 1 ] && set "required" "$1" && break OPTARG=$2 PARAM="$OPTARG" shift ;; '-o'|'--option') set -- "$1" "$@" [ ${OPTARG+x} ] && { case $1 in --no-*|--without-*) set "noarg" "${1%%\=*}"; break; esac [ "${OPTARG:-}" ] && { shift; OPTARG=$2; } || OPTARG='default' } || OPTARG='' OPTION="$OPTARG" shift ;; '-h'|'--help') usage exit 0 ;; '--version') echo "${VERSION}" exit 0 ;; --) shift while [ $# -gt 0 ]; do REST="${REST} \"\${$(($OPTIND-$#))}\"" shift done break ;; [-]?*) set "unknown" "$1"; break ;; *) REST="${REST} \"\${$(($OPTIND-$#))}\"" esac shift done [ $# -eq 0 ] && { OPTIND=1; unset OPTARG; return 0; } case $1 in unknown) set "Unrecognized option: $2" "$@" ;; noarg) set "Does not allow an argument: $2" "$@" ;; required) set "Requires an argument: $2" "$@" ;; pattern:*) set "Does not match the pattern (${1#*:}): $2" "$@" ;; notcmd) set "Not a command: $2" "$@" ;; *) set "Validation error ($1): $2" "$@" esac echo "$1" >&2 exit 1 } usage() { cat<<'GETOPTIONSHERE' Usage: example.sh [options]... [arguments]... Options: -f, --flag takes no arguments -p, --param PARAM takes one argument -o, --option[=OPTION] takes one optional argument -h, --help --version GETOPTIONSHERE }
zparseopts -D -E -M -- d=debug -debug=dAnd have both-dand--debugin the$debugarrayecho $+debug[1]will return 0 or 1 if one of those are used. Ref: zsh.org/mla/users/2011/msg00350.html=separating option name from option value (in both cases, it simply assumes that the option value is in the next argument). It also doesn't handle short option clustering — the question didn't need it.$1,$2, etc., 2) flags withgetoptsand${OPTARG}, 3) looping over all parameters ($@), and 4) looping over all parameters using$#,$1, and theshiftoperator.