Search code examples
bashshellparametersgetopts

How to combine getopts and positional parameters in bash?


I want to use both getopts and positional parameters, but if I pass in a positional parameter to the program the getopts get lost.

directory=$1

while getopts l: flag; do
  case "$flag" in
    l) level=$OPTARG;;
  esac
done

if [ -n "$level" ]; then
  echo "Level exist!"
else
  echo "Level doesn't exist!"
fi

So when I run the program like this:

sh myprogram.sh ~/documents -l 2

I expect:

Level exist!

And instead it returns:

Level doesn't exist!

The thing is, if I run the program without the positional parameter (~/documents) like this:

sh myprogram.sh -l 2

I get the correct output:

Level exist!

Why is that? How can I use both positional parameters and getopts in bash?

Thanks!


Solution

  • Most tools are written in the form: tool [options] arg ...

    So you would do this:

    # first, parse the options:
    while getopts l: flag; do
      case "$flag" in
        l) level=$OPTARG;;
        \?) exit 42;;
      esac
    done
    
    # and shift them away
    shift $((OPTIND - 1))
    
    # validation
    if [ -n "$level" ]; then
      echo "Level exist!"
    else
      echo "Level doesn't exist!"
    fi
    
    # THEN, access the positional params
    echo "there are $# positional params remaining"
    for ((i=1; i<=$#; i++)); do
      printf "%d\t%s\n" $i "${!i}"
    done
    

    Use the \? to abort the script if the user provides an unknown option or fails to provide a required argument

    And invoke it like:

    $ bash test.sh
    Level doesn't exist!
    there are 0 positional params remaining
    
    $ bash test.sh -l 2
    Level exist!
    there are 0 positional params remaining
    
    $ bash test.sh -l 2 foo bar
    Level exist!
    there are 2 positional params remaining
    1   foo
    2   bar
    
    $ bash test.sh -x
    test.sh: illegal option -- x
    
    $ bash test.sh -l
    test.sh: option requires an argument -- l
    

    But you cannot put the options after the arguments: getopts stops when the first non-option argument is found

    $ bash test.sh foo bar -l 2
    Level doesn't exist!
    there are 4 positional params remaining
    1   foo
    2   bar
    3   -l
    4   2