-2

I want to understand how OPTIND works by getopts. If I want to skip the first few positional arguments, how should I set up OPTIND exactly ?

And because OPTIND is not reset automatically, I need to know how exactly to manually reset between multiple calls to getopts.

Because on the first call I get the error being reported

gopi -z
/usr/local/bin/bash: option requires an argument -- z

But the second call, the error is not reported

gopi -z

This is the function

 gopi ()
 {

local parg="" while (( $# > 0 )); do parg="$1" case $parg in ("-s"|"--silent") opstring=":n:z:" ;; (*) break ;; esac # case ends here shift 1 done

while getopts "$opstring" opname; do case ${opname} in ("n") dothis ;; ("z") dothat ;; (?) ## Invalid Option Found echo "Invalid option: -$OPTARG" 1>&2 exit 1 ;; (:) ## Required option argument not found echo "Option -$OPTARG requires an argument" 1>&2 exit 1 ;; esac done }

Vera
  • 1,223
  • 3
    I’m voting to close this question because the OP is multi-posting on multiple sites from multiple logins as usual. – Ed Morton May 07 '23 at 14:45

1 Answers1

2

And because OPTIND is not reset automatically, I need to know how exactly to manually reset between multiple calls to getopts.

Bash's man page says (under the getopts description):

OPTIND is initialized to 1 each time the shell or a shell script is invoked.

which hints that resetting it to 1 should work. This is more clearly spelled out in the POSIX description of getopts:

If the application sets OPTIND to the value 1, a new set of parameters can be used: either the current positional parameters or new arg values. Any other attempt to invoke getopts multiple times in a single shell execution environment with parameters (positional parameters or arg operands) that are not the same in all invocations, or with an OPTIND value modified to be a value other than 1, produces unspecified results.

Here, the second call to foo misses the first argument -a, but the last call to foo again sees both arguments:

foo() {
    echo --
    while getopts abc opt; do
        echo $opt
    done
}
foo -abc
foo -a -b
OPTIND=1
foo -a -b

Of course it makes more sense to set OPTIND=1 at the beginning of the function, before calling getopts.


Note that it's not really useful to look into the value of OPTIND. It's not the full truth, as it doesn't contain information about the position inside a single command line argument. E.g. with the argument -abc in the first invocation of foo above, OPTIND takes the values 1, 1, 2 within the loop, with the current character position invisible. (In Bash and ksh, that is. Zsh seems to reset OPTIND when entering a function, and yash encodes the position within an argument in the OPTIND value.)

ilkkachu
  • 138,973
  • You are quite right about the usefulness of looking at OPTIND. It is a variable important for getepts itself, but certainly not for users. Values for OPTIND got shown for option arguments, meaning that using echo "OPTIND: $OPTIND just after the while clause does not print each positional argument one by one, many are skipped because of the way that gotepts processes the positional parameters. – Vera May 07 '23 at 11:07
  • What I need to do is OPTIND=1 every time the function is called. Perhaps one should include OPTIND=1 as the first thing to do in the function, but it is reset every time the function is called? – Vera May 07 '23 at 11:08
  • @Ephram, yes, it would make sense to set OPTIND=1 as the first thing in the function. I just didn't do that here for the sake demonstration. – ilkkachu May 07 '23 at 11:15
  • Would it make sense to define OPTIND as a local variable, i.e. local OPTIND=1 ? – Vera May 07 '23 at 11:23
  • @Ephram, I don't know, try it :) I don't think you'll get full use of it being local in the shells that have the hidden variable for the character position, since because of that you likely still couldn't call a function using getopts from with a while getopts loop. – ilkkachu May 07 '23 at 13:25
  • If I have multiple functions using it,could there be a conflict? – Vera May 07 '23 at 13:56
  • After calling getopts I am also doing shift "$(( OPTIND - 1 ))" as the value would be on the position of the next argument after the options defined by getopts. – Vera May 07 '23 at 13:58
  • Doing shift "$(( OPTIND - 1 ))" is good right ? Or are the quotes not needed ? – Vera May 08 '23 at 18:44
  • @Ephram, right, yes, shift "$(( OPTIND - 1 ))" would be the way to get rid of the options that were processed in the getopts loop. You probably don't need the quotes there, but if for whatever reason IFS contains some of the digits that appear in the result, then without the quotes, the result would be mangled. E.g. like in IFS=2; foo=123; echo $foo. If you need to ask about quoting expansion in POSIXy shells, just quote them. :) – ilkkachu May 08 '23 at 19:00
  • You are right, I get a space instead of the digit 2. Whilst with quoting I get the correct number printed. – Vera May 08 '23 at 21:40
  • Originally I defined OPTIND as a local variable, but realised it only makes sense for it to be a global variable. – Vera May 08 '23 at 21:42