Bash function arguments strange behaviour

argumentsbashfunctiongetopts

I have the following bash function:

lscf() {
  while getopts f:d: opt ; do
    case $opt in
      f) file="$OPTARG" ;;
      d) days="$OPTARG" ;;
    esac
  done

  echo file is $file
  echo days is $days
}

Running this with arguments does not output any values. Only after running the function without arguments, and then again with arguments does it output the correct values:

-bash-4.1$ lscf -d 10 -f file.txt
file is
days is

-bash-4.1$ lscf
file is
days is

-bash-4.1$ lscf -d 10 -f file.txt
file is file.txt
days is 10

Am I missing something?

Best Answer

Though I can't reproduce the initial run of the function that you have in your question, you should reset OPTIND to 1 in your function to be able to process the function's command line in repeated invocations of it.

From the bash manual:

OPTIND is initialized to 1 each time the shell or a shell script is invoked. When an option requires an argument, getopts places that argument into the variable OPTARG. The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used.

From the POSIX standard:

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.

The "shell invocation" that the bash manual mentions is the same as the "single execution environment" that the POSIX text mentions, and both refer to your shell script or interactive shell. Within the script or interactive shell, multiple calls to your lscf will invoke getopts in the same environment, and OPTIND will need to be reset to 1 before each such invocation.

Therefore:

lscf() {
  OPTIND=1

  while getopts f:d: opt ; do
    case $opt in
      f) file="$OPTARG" ;;
      d) days="$OPTARG" ;;
    esac
  done

  echo file is $file
  echo days is $days
}

If the variables file and days should not be set in the calling shell's environment, they should be local variables. Also, quote variable expansions and use printf to output variable data:

lscf() {
  local file
  local days

  OPTIND=1

  while getopts f:d: opt ; do
    case $opt in
      f) file="$OPTARG" ;;
      d) days="$OPTARG" ;;
    esac
  done

  printf 'file is %s\n' "$file"
  printf 'days is %s\n' "$days"
}