Handling options with optional arguments with getopts
‐‐‐ Original Message ‐‐‐ On Friday, August 27, 2021 4:02 PM, Robert Elz wrote: > Date:Fri, 27 Aug 2021 15:05:52 + > From: nigelberlinguer via Bug reports for the GNU Bourne Again > SHell > > Message-ID: > <0IgsinjPxg5VSubCxyc64u9axdDTEubUNcQFmIaPyduotl2CyQ9g71uoLtpmXL2hUph1_eHzVRnEZ7vyyHFKqqy3OlPydQXccd2CkHyzpjA=@protonmail.com> > > > | I am trying to use getopts so one of the options can use > | an optional argument. > > getopts is required by POSIX to: > > The getopts utility shall retrieve options and option-arguments > from a list of parameters. It shall support the Utility Syntax > Guidelines 3 to 10, inclusive, described in XBD Section 12.2 > > XBD 12.2 guideline 7 is: > > Guideline 7: Option-arguments should not be optional. > > That is, if you want to be able to give an option arg, or not give one, > those should be implemented as 2 separate options. It should be noted though, that the POSIX requirement by "Guideline 7" is not guided by actual portability in the technical sense but by a rule written in the POSIX standard. Perhaps there should be an update to POSIX on what is actually portable or not > That said, getopts also: > > If an option-argument is missing: > > If the first character of optstring is a , > > the shell variable specified by name shall be set to the >character and the shell variable OPTARG shall be > > set to the option character found. > > > which means that you can do > > while getopts :abc:d var > do > case "${var}" in > a) ;; > b) ;; > c) carg=${OPTARG}; ;; > d) ;; > :) case "${OPTARG}" in > c) carg=Missing; ;; > *) usage ... ;; > esac > ?) usage Bad arg "${OPTARG}" ... ;; > esac > done > > or something like that ... but beware that it only actually works > when the option that might have an arg is last in the args given, so > in the case above > > script -a -c > > would do what you want, but > > script -c -a > > would not, there "-a" is the arg to the -c option, not an option itself. > There is no way (certainly no portable way) around that. > > kre I have seen the following workaround, where tho options that allows an optional argument is defined with no arguments in shortopts. local vb=1 sort=0 local OPTIND OPTARG local shortopts="Vuhvs" while getopts $shortopts arg; do case $arg in ("V") printf '%s\n' "Version" ; return ;; ("u") printf '%s\n' "usage" ; return ;; ("h") printf '%s\n' "help"; return ;; ("v") # Allows argument to be optional. # Defines option with no arguments in shortopts. nextarg=${!OPTIND} if [[ (-n "$nextarg") && ("$nextarg" != -*) ]] ; then OPTIND=$((OPTIND + 1)) vb="$nextarg" fi ;; #. ("s") sort=1 ; shift ;; # shifts arg by 1 #. (?) pfm "Invalid option: -${OPTARG}." pfm "Invoke \`myfunc -h\` for details." break ;; esac done I also wonder whether the "shift" command is used with `getopts`. I see people use the `shift` command when doing their own parsing; and when others use `getopt`.
Handling options with optional arguments with getopts
‐‐‐ Original Message ‐‐‐ On Friday, August 27, 2021 8:52 PM, Robert Elz wrote: > Date:Fri, 27 Aug 2021 17:20:39 + > From: nigelberlinguer > > Message-ID: > > > > | It should be noted though, that the POSIX requirement by "Guideline 7" > | is not guided by actual portability in the technical sense but by a > | rule written in the POSIX standard. > > Those guidelines serve two purposes - they indicate what should be the > arg format for posix standard utilities, and they specify what must be > handled for those cases where they are specified to apply (as in getopts). > > | Perhaps there should be an update > | to POSIX on what is actually portable or not > > There are constantly updates to POSIX - but I don't see anything likely to > change in this area. "Works in bash" is not the definition of portable. > > In general, what POSIX specifies (with just a few exceptions, which > often don't matter) is what you can rely upon working - as soon as you > start using anything not specified by POSIX, or explicitly said > to be unspecified or undefined, then you cannot really expect the > code (including scripts) to work on other systems, and perhaps not > even on later versions of the system you're using. > > | I have seen the following workaround, where tho options that allows an > | optional argument is defined with no arguments in shortopts. > | > | local vb=1 sort=0 > | > | local OPTIND OPTARG > | local shortopts="Vuhvs" > | while getopts $shortopts arg; do > | case $arg in > | ("V") printf '%s\n' "Version" ; return ;; > | ("u") printf '%s\n' "usage" ; return ;; > | ("h") printf '%s\n' "help" ; return ;; > | ("v") > | # Allows argument to be optional. > | # Defines option with no arguments in shortopts. > | nextarg=${!OPTIND} > | if [[ (-n "$nextarg") && ("$nextarg" != -*) ]] ; then > | OPTIND=$((OPTIND + 1)) > | vb="$nextarg" > > Aside from using bash private syntax (which could be mostly avoided there > if one had the desire) that kind of use of OPTIND is certainly not portable. > The only defined write operation on OPTIND is to set it to 1. > > Further, even where something like that does work, it provides no mechanism > for the arg to the option to begin with a '-', which might not matter in > some cases, but certainly isn't very general. > > | I also wonder whether the "shift" command is used with `getopts`. > > No. Or not inside the loop. Once the loop is finished, the code > should usually do > > shift $(( ${OPTIND} - 1 )) > > to remove all the args that have been processed by getopts - but that's > not always required (there are other ways to get the remaining args, if > any, if they are needed, but doing the shift means the remaining args are > "$@"). > > Altering the arg list (in any way at all) during getopts processing produces > unspecified results. > > | I see people use the `shift` command when doing their own parsing; > > Yes, that's often the easiest way to do it for hand rolled parsing > (it means that what you're currently examining is always $1, and so > there's no need to write messy code to get at a variable positional param, > which is not trivial to do portably). > > | and when others use `getopt`. > > getopt is obsolete, and has numerous failure modes. But yes, shift > is used when using getopt (getopt is generally implemented as an > external command, and so cannot affect the state of the shell, including > any shell variables, getopts is always a shell builtin command). > > kre Thank you very much for the explanation.
Handling options with optional arguments with getopts
‐‐‐ Original Message ‐‐‐ On Friday, August 27, 2021 8:52 PM, Robert Elz wrote: > Date:Fri, 27 Aug 2021 17:20:39 + > From: nigelberlinguer > > Message-ID: > > > > | It should be noted though, that the POSIX requirement by "Guideline 7" > | is not guided by actual portability in the technical sense but by a > | rule written in the POSIX standard. > > Those guidelines serve two purposes - they indicate what should be the > arg format for posix standard utilities, and they specify what must be > handled for those cases where they are specified to apply (as in getopts). > > | Perhaps there should be an update > | to POSIX on what is actually portable or not > > There are constantly updates to POSIX - but I don't see anything likely to > change in this area. "Works in bash" is not the definition of portable. > > In general, what POSIX specifies (with just a few exceptions, which > often don't matter) is what you can rely upon working - as soon as you > start using anything not specified by POSIX, or explicitly said > to be unspecified or undefined, then you cannot really expect the > code (including scripts) to work on other systems, and perhaps not > even on later versions of the system you're using. Would parsing things yourself be made portable ? > | I have seen the following workaround, where tho options that allows an > | optional argument is defined with no arguments in shortopts. > | > | local vb=1 sort=0 > | > | local OPTIND OPTARG > | local shortopts="Vuhvs" > | while getopts $shortopts arg; do > | case $arg in > | ("V") printf '%s\n' "Version" ; return ;; > | ("u") printf '%s\n' "usage" ; return ;; > | ("h") printf '%s\n' "help" ; return ;; > | ("v") > | # Allows argument to be optional. > | # Defines option with no arguments in shortopts. > | nextarg=${!OPTIND} > | if [[ (-n "$nextarg") && ("$nextarg" != -*) ]] ; then > | OPTIND=$((OPTIND + 1)) > | vb="$nextarg" > > Aside from using bash private syntax (which could be mostly avoided there > if one had the desire) that kind of use of OPTIND is certainly not portable. > The only defined write operation on OPTIND is to set it to 1. > > Further, even where something like that does work, it provides no mechanism > for the arg to the option to begin with a '-', which might not matter in > some cases, but certainly isn't very general. One can check amongst the options defined, and allow everything else. > | I also wonder whether the "shift" command is used with `getopts`. > > No. Or not inside the loop. Once the loop is finished, the code > should usually do > > shift $(( ${OPTIND} - 1 )) > > to remove all the args that have been processed by getopts - but that's > not always required (there are other ways to get the remaining args, if > any, if they are needed, but doing the shift means the remaining args are > "$@"). > > Altering the arg list (in any way at all) during getopts processing produces > unspecified results. > > | I see people use the `shift` command when doing their own parsing; > > Yes, that's often the easiest way to do it for hand rolled parsing > (it means that what you're currently examining is always $1, and so > there's no need to write messy code to get at a variable positional param, > which is not trivial to do portably). > > | and when others use `getopt`. > > getopt is obsolete, and has numerous failure modes. But yes, shift > is used when using getopt (getopt is generally implemented as an > external command, and so cannot affect the state of the shell, including > any shell variables, getopts is always a shell builtin command). > > kre
Handling options with optional arguments with getopts
I am trying to use getopts so one of the options can use an optional argument. Have seen many discussions online about using getopts to handle options with optional arguments, But also a lot of confusing arguments against many workarounds. Could bash getopts have a natural way to introduce an option with a optional argument, and have the procedure documented in the bash manual, with an example, if you please.