64

I'd like to use getopts inside a function that I have defined in my .bash_profile. The idea is I'd like to pass in some flags to this function to alter its behavior.

Here's the code:

function t() {
    echo $*
    getopts "a:" OPTION
    echo $OPTION
    echo $OPTARG
}

When I invoke it like this:

t -a bc

I get this output:

-a bc
?
 

What's wrong? I'd like to get the value bc without manually shifting and parsing. How do I use getopts correctly inside a function?

EDIT: corrected my code snippet to try $OPTARG, to no avail

EDIT #2: OK turns out the code is fine, my shell was somehow messed up. Opening a new window solved it. The arg value was indeed in $OPTARG.

3 Answers 3

137

As @Ansgar points out, the argument to your option is stored in ${OPTARG}, but this is not the only thing to watch out for when using getopts inside a function. You also need to make sure that ${OPTIND} is local to the function by either unsetting it or declaring it local, otherwise you will encounter unexpected behaviour when invoking the function multiple times.

t.sh:

#!/bin/bash

foo()
{
    foo_usage() { echo "foo: [-a <arg>]" 1>&2; exit; }

    local OPTIND o a
    while getopts ":a:" o; do
        case "${o}" in
            a)
                a="${OPTARG}"
                ;;
            *)
                foo_usage
                ;;
        esac
    done
    shift $((OPTIND-1))

    echo "a: [${a}], non-option arguments: $*"
}

foo
foo -a bc bar quux
foo -x

Example run:

$ ./t.sh
a: [], non-option arguments:
a: [bc], non-option arguments: bar quux
foo: [-a <arg>]

If you comment out # local OPTIND, this is what you get instead:

$ ./t.sh
a: [], non-option arguments:
a: [bc], non-option arguments: bar quux
a: [bc], non-option arguments:

Other than that, its usage is the same as when used outside of a function.

Sign up to request clarification or add additional context in comments.

5 Comments

1.) The 1 in 1>&2 is not necessary. 2.) You missed to define a, o and OPTARG as local. 3.) The exit will not exit the script but just a sub-shell. In order to exit the script it is necessary to set -e in the outer shell and exit 1 in the sub-shell. The example does not trigger the problem, but a MSG=$(foo ...) will do it.
@ceving 1) This is a matter of coding style but, no, it's not necessary as per the language definition. 2) Agreed, those should be local. 3) As you said, the exit will exit the script in my example. Of course exit won't exit sub shells but this is not a specific problem to this question. You don't have to use set -e, you just have to make sure to catch the error and MSG=$(foo ...) || die works just as well. set -e is one solution to the problem but it's not idiot-proof and I, as many others, don't recommend using it.
Just pointing out that even if you're not using OPTIND in the function (e.g., with shift;), it still needs to be localised in order to have predictable behaviour.
@ceving, you can look here, stackoverflow.com/questions/61566331/… - I added local but did not helped
I asked Gemini Advanced a similar question and it got it wrong - I am glad to stumble upon this that helped me.
19

Here is simple example of getopts usage within shell function:

#!/usr/bin/env bash
t() {
  local OPTIND
  getopts "a:" OPTION
  echo Input: $*, OPTION: $OPTION, OPTARG: $OPTARG
}
t "$@"
t -a foo

Output:

$ ./test.sh -a bc
Input: -a bc, OPTION: a, OPTARG: bc
Input: -a foo, OPTION: a, OPTARG: foo

As @Adrian pointed out, local OPTIND (or OPTIND=1) needs to be set as shell does not reset OPTIND automatically between multiple calls to getopts (man bash).

The base-syntax for getopts is:

getopts OPTSTRING VARNAME [ARGS...]

and by default, not specifying arguments is equivalent to explicitly calling it with "$@" which is: getopts "a:" opts "$@".

In case of problems, these are the used variables for getopts to check:

  • OPTIND - the index to the next argument to be processed,
  • OPTARG - variable is set to any argument for an option found by getopts,
  • OPTERR (not POSIX) - set to 0 or 1 to indicate if Bash should display error messages generated by the getopts.

Further more, see: Small getopts tutorial at The Bash Hackers Wiki

2 Comments

Very thoughtfully laid out answer. I don't know if it works, but great job.
@kenorb, you can look here -> stackoverflow.com/questions/61566331/…, added local but did not helped
6

The argument is stored in the varable $OPTARG.

function t() {
  echo $*
  getopts "a:" OPTION
  echo $OPTION
  echo $OPTARG
}

Output:

$ t -a bc
-a bc
a
bc

5 Comments

Sorry I incorrectly pasted my code snippet... I also echo $OPTARG, that's the 3rd line which is blank. Any other ideas?
@Magnus Probably because you called the function several times and $OPTIND isn't defined locally (see Adrian's answer). While I'm grateful that you accepted my answer, you should probably rather accept his instead.
The code works only when pasting into shell, but not in the script.
@kenorb It works just fine from a script. I'm not making this up.
Ok, I wasn't sure how t is called, I had to call as t -a bc inside the script or add t "$@", initially I thought it's going to work without.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.