8

I defined the function f in Bash based on the example here (under "An option with an argument"):

f () {
  while getopts ":a:" opt; do
    case $opt in
      a)
        echo "-a was triggered, Parameter: $OPTARG" >&2
        ;;
      \?)
        echo "Invalid option: -$OPTARG" >&2
        return 1
        ;;
      :)
        echo "Option -$OPTARG requires an argument." >&2
        return 1
        ;;
    esac
  done
}

Whereas they use a script, I directly define the function in the shell.

When I first launch Bash and define the function, everything works: f -a 123 prints -a was triggered, Parameter: 123. But when I run the exact same line a second time, nothing is printed.

What's causing this behavior? It happens in Bash 3.2 and 4.3, but it works fine in Zsh 5.1. This is surprising because the example was supposed to be for Bash, not for Zsh.

14

bash getopts use an environment variable OPTIND to keep track the last option argument processed. The fact that OPTIND was not automatically reset each time you called getopts in the same shell session, only when the shell was invoked. So from second time you called getopts with the same arguments in the same session, OPTIND wasn't changed, getopts thought it had done the job and do nothing.

You can reset OPTIND manually to make it work:

$ OPTIND=1
$ f -a 123
-a was triggered, Parameter: 123

or just put the function into a script and call the script multiple times.


zsh getopts is slightly different. OPTIND was normally reset to 1 each time upon exit from shell function.

  • I'm pedantically accepting this answer because it's slightly more complete than the other one – shadowtalker Oct 3 '15 at 18:04
  • Added unset opt OPTARG OPTIND before every while getopts... call and it now works flawlessly. Thanks :) – Deep Oct 6 '18 at 9:41
6

It is a god habit to declare local variables in any function. If you declare $opt, $OPTARG and $OPTIND then getopts will work any times you call the function. Local variables are discarded after the function finished.

#!/bin/bash
function some_func {
  declare opt
  declare OPTARG
  declare OPTIND

  while getopts ":a:" opt; do
    echo $opt is $OPTARG
  done
}
  • 1
    Just declaring didn't work for me. I had to put unset opt OPTARG OPTIND before the while getopts... statement such that the value of each of these variables was unset. If I didn't do this and just did the declare, since in the given bash session where I had already used getopts once the OPTIND was present, it remained unchanged – Deep Oct 6 '18 at 9:40
2

You need to set OPTIND=1 at the start of function f. By default it is 1, but then gets incremented as your args are parsed. When you call getopts again it carries on where it left off. You can see this if your 2nd call is:

f -a 123 -a 999

when it will print the 999.

  • Of course! Coming from R and Python I always get tripped up by scoping in the shell. Will it work if I declare local OPTIND inside the function before getopts is called? – shadowtalker Oct 3 '15 at 18:03
  • 1
    should do, good solution. – meuh Oct 3 '15 at 18:06
1

When getopt is called it keep track of processed options by the variable OPTIND.

Try the following:

#!/bin/bash

f () {
    printf "Intro OPTIND: %d\n" "$OPTIND"
    while getopts ":a:b:" opt; do
        printf "Current OPTIND: %d\n" "$OPTIND"
        case $opt in
            a)
                echo "-a was triggered, Parameter: $OPTARG" >&2
                ;;
            b)
                echo "-b was triggered, Parameter: $OPTARG" >&2
                ;;
        esac
    done
    printf "Exit OPTIND: %d\n" "$OPTIND"
}

echo "Run #1"
f "$@"
echo "Run #2"
f "$@"

Yield:

./test -a foo -b bar
Run #1
Intro OPTIND: 1
Current OPTIND: 3
-a was triggered, Parameter: foo
Current OPTIND: 5
-b was triggered, Parameter: bar
Exit OPTIND: 5
Run #2
Intro OPTIND: 5
Exit OPTIND: 5

As such you could do something like:

OPTIND=1

at start of the function. Or the, depending on situation, and usually better:

local OPTIND

If OPTIND was not used, as the function is implemented, the while loop would go forever. One can also use it to resume processing of arguments, after fail or what ever, call a different function if x or y and it will resume where previous left off etc.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.