Use getopts to Parse Linux Shell Script Options - WindowsTips.net - Windows Tips and Tricks with Geek

Tuesday, February 22, 2022

Use getopts to Parse Linux Shell Script Options

 

A laptop screen showing terminal text.

Note: getopts Not getopt

There’s an older utility called getopt . This is a small utility program, not a builtin. There are many different versions of getopt with differing behaviors, whereas the getops builtin follows POSIX guidelines.

type getopts
type getopt

using the type command to see the difference between getop and getops

Because getopt isn’t a builtin it doesn’t share some of the automatic benefits that getopts  does, such as handling whitespace sensibly. With getopts, the Bash shell is running your script and the Bash shell is doing the option parsing. You don’t need to invoke an external program to handle the parsing.

The tradeoff is getopts doesn’t handle double-dashed, long-format option names. So you can use options formatted like -w  but not ” ---wide-format.” On the other hand, if you have a script that accepts the options -a , -b , and  -c , getopts lets you combine them like -abc-bca, or -bac and so on.

We’re discussing and demonstrating  getopts in this article, so make sure you add the final “s” to the command name.

A Quick Recap: Handling Parameter Values

This script doesn’t use dashed options like -a or -b . It does accept “normal” parameters on the command line and these are accessed inside the script as values.

#!/bin/bash

# get the variables one by one
echo "Variable One: $1" 
echo "Variable Two: $2" 
echo "Variable Three: $3"

# loop through the variables
for var in "$@" do 
  echo "$var" 
done

The parameters are accessed inside the script as variables $1$2, or $3 .

Copy this text into an editor and save it as a file called “variables.sh.” We’ll need to make it executable with the chmod command. You’ll need to do this step for all of the scripts we discuss. Just substitute the name of the appropriate script file each time.

chmod +x variables.sh

using the chmod command to make a script executable

If we run our script with no parameters, we get this output.

./variables.sh

running a script with no parameters

We passed no parameters so the script has no values to report. Let’s provide some parameters this time.

./variables.sh how to geek

running a script with three words as its parameters

As expected, the variables $1$2 , and $3 have been set to the parameter values and we see these printed.

This type of one-for-one parameter handling means we need to know in advance how many parameters there will be. The loop at the bottom of the script doesn’t care how many parameters there are, it always loops through them all.

If we provide a fourth parameter, it isn’t assigned to a variable, but the loop still handles it.

./variables.sh how to geek website

passing four parameters to a script that can only handle three

If we put quotation marks around two of the words they’re treated as one parameter.

./variables.sh how "to geek"

quoting two command line parameters to have them treated as one parameter

If we’re going to need our script to handle all combinations of options, options with arguments, and “normal” data type parameters, we’re going to need to separate the options from the regular parameters. We can achieve that by placing all options—with or without arguments—before the regular parameters.

But let’s not run before we can walk. Let’s look at the simplest case for handling command-line options.

Handling Options

We use getopts in a while loop. Each iteration of the loop works on one option that was passed to the script. In each case, the variable OPTION is set to the option identified by getopts.

With each iteration of the loop, getopts moves on to the next option. When there are no more options, getopts returns false and the while loop exits.


The individual clauses in the case statement make it easy to perform option-specific actions within the script. Typically, in a real-world script, you’d set a variable in each clause, and these would act as flags further on in the script, allowing or denying some functionality.

The OPTION variable is matched against the patterns in each of the case statement clauses. Because we’re using a case statement, it doesn’t matter what order the options are provided on the command line. Each option is dropped into the case statement and the appropriate clause is triggered.

Copy this text into an editor and save it as a script called “options.sh”, and make it executable.

#!/bin/bash

while getopts 'abc' OPTION; do
  case "$OPTION" in 
    a) 
      echo "Option a used" ;;

    b)
      echo "Option b used"
      ;;

    c)
      echo "Option c used"
      ;;

    ?) 
      echo "Usage: $(basename $0) [-a] [-b] [-c]"
      exit 1
      ;;
  esac
done

This is the line that defines the while loop.

while getopts 'abc' OPTION; do

The getopts command is followed by the options string. This lists the letters that we’re going to use as options. Only letters in this list can be used as options. So in this case, -d would be invalid. This would be trapped by the ?) clause because getopts returns a question mark “?” for an unidentified option. If that happens the correct usage is printed to the terminal window:

echo "Usage: $(basename $0) [-a] [-b] [-c]"

By convention, wrapping an option in brackets “[]” in this type of correct usage message means the option is optional. The basename command strips any directory paths from the file name. The script file name is held in $0 in Bash scripts.

Let’s use this script with different command line combinations.

./options.sh -a
./options.sh -a -b -c
./options.sh -ab -c
./options.sh -cab

testing a script that can accept switch type command line options

As we can see, all of our test combinations of options are parsed and handled correctly. What if we try an option that doesn’t exist?

./options.sh -d

An unrecognized option being reported by the shell and the script

The usage clause is triggered, which is good, but we also get an error message from the shell. That might or might not matter to your use case. If you’re calling the script from another script that has to parse error messages, it’ll make it more difficult if the shell is generating error messages too.

Turning off the shell error messages is very easy. All we need to do is put a colon ” : ” as the first character of the options string.

Either edit your “options.sh” file and add a colon as the first character of the options string, or save this script as “options2.sh”, and make it executable.

#!/bin/bash

while getopts ':abc' OPTION; do
  case "$OPTION" in 
    a) 
      echo "Option a used" 
      ;;

    b)
      echo "Option b used"
      ;;

    c)
      echo "Option c used"
      ;;

    ?) 
      echo "Usage: $(basename $0) [-a] [-b] [-c]"
      exit 1
      ;;
  esac
done

When we run this and generate an error, we receive our own error messages without any shell messages.

./options2.sh.sh -d

An unrecognized option being reported by script alone

Using getopts With Option Arguments

To tell getopts that an option will be followed by an argument, put a colon ” : ” immediately behind the option letter in the options string.

If we follow the “b” and “c” in our options string with colons, getopt will expect arguments for these options. Copy this script into your editor and save it as “arguments.sh”, and make it executable.

Remember, the first colon in the options string is used to suppress shell error messages—it has nothing to do with argument processing.

When getopt processes an option with an argument, the argument is placed in the OPTARG variable. If you want to use this value elsewhere in your script, you’ll need to copy it to another variable.

#!/bin/bash

while getopts ':ab:c:' OPTION; do

  case "$OPTION" in
    a)
      echo "Option a used"
      ;;

    b)
      argB="$OPTARG"
      echo "Option b used with: $argB"
      ;;

    c)
      argC="$OPTARG"
      echo "Option c used with: $argC"
      ;;

    ?)
      echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]"
      exit 1
      ;;
  esac

done

Let’s run that and see how it works.

./arguments.sh -a -b "how to geek" -c reviewgeek
./arguments.sh -c reviewgeek -a

testing a script that can handle option arguments

So now we can handle options with or without arguments, regardless of the order in which they’re given on the command line.

But what about regular parameters? We said earlier we knew we’d have to put those on the command line after any options. Let’s see what happens if we do.

Mixing Options and Parameters

We’ll change our previous script to include one more line. When the while loop has exited and all of the options have been handled we’ll try to access the regular parameters. We’ll print out the value in $1 .

Save this script as “arguments2.sh”, and make it executable.

#!/bin/bash

while getopts ':ab:c:' OPTION; do

  case "$OPTION" in
    a)
      echo "Option a used"
      ;;

    b)
      argB="$OPTARG"
      echo "Option b used with: $argB"
      ;;

    c)
      argC="$OPTARG"
      echo "Option c used with: $argC"
      ;;

    ?)
      echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]"
      exit 1
      ;;
  esac

done

echo "Variable one is: $1"

Now we’ll try a few combinations of options and parameters.

./arguments2.sh dave
./arguments2.sh -a dave
./arguments2.sh -a -c how-to-geek dave

Failing to access standard parameters in a script that accepts option arguments

So now we can see the problem. As as soon as any options are used, the variables $1 onwards are filled with the option flags and their arguments. In the last example, $4 would hold the parameter value “dave”, but how do you access that in your script if you don’t know how many options and arguments are going to be used?

The answer is to use OPTIND and the shift command.

The shift command discards the first parameter—regardless of type—from the parameter list. The other parameters “shuffle down”, so parameter 2 becomes parameter 1, parameter 3 becomes parameter 2, and so on. And so $2 becomes $1 , $3 becomes $2 , and so on.

If you provide shift with a number, it’ll take that many parameters off the list.

OPTIND counts the options and arguments as they are found and processed. Once all the options and arguments have been processed OPTIND will be one higher than the number of options. So if we use shift to trim (OPTIND-1) parameters off the parameter list, we’ll be left with the regular parameters in $1 onwards.

That’s exactly what this script does. Save this script as “arguments3.sh” and make it executable.

#!/bin/bash

while getopts ':ab:c:' OPTION; do
  case "$OPTION" in
    a)
      echo "Option a used"
      ;;

    b)
      argB="$OPTARG"
      echo "Option b used with: $argB"
      ;;

    c)
      argC="$OPTARG"
      echo "Option c used with: $argC"
      ;;

    ?)
      echo "Usage: $(basename $0) [-a] [-b argument] [-c argument]"
      exit 1
      ;;
  esac
done

echo "Before - variable one is: $1"
shift "$(($OPTIND -1))"
echo "After - variable one is: $1"
echo "The rest of the arguments (operands)"

for x in "$@"
do
  echo $x
done

We’ll run this with a mix of options, arguments, and parameters.

./arguments3.sh -a -c how-to-geek "dave dee" dozy beaky mick tich

Correctly accessing standard parameters in a script that accepts option arguments

We can see that before we called shift , $1 held “-a”, but after the shift command $1 holds our first non-option, non-argument parameter. We can loop through all of the parameters just as easily as we can in a script with no option parsing.

It’s Always Good To Have Options

Handling options and their arguments in scripts doesn’t need to be complicated. With getopts you can create scripts that handle command-line options, arguments, and parameters exactly like POSIX compliant native scripts should.

No comments:

Post a Comment