How to Use Bash Getopts With Examples

Getopts is a powerful shell command used to parse command line options and arguments. It provides a structured way to handle complex input patterns, thereby making your scripts more readable and maintainable.

In this blog post, we’ll start with an understanding of what positional parameters are and their inherent limitations. Then, we'll understand how getopts overcomes these limitations. We'll discuss what getopts is, its syntax, and how it can be effectively used. Let’s get started!

Prerequisite

To try out the scripts in this blog post, you need access to a Bash shell. You also need a text editor, such as "nano" or "vim", which come pre-installed by default in many Unix-like operating systems.

For the purpose of this blog post, I'll be using KodeKloud’s Ubuntu playground, which lets you access a pre-installed Ubuntu operating system in just one click. Best of all, you won't need to go through the hassle of installing any additional software— everything you need is already set up and ready to use.

Create a Script File

Let’s start by creating a Bash script file named demo.sh. This is where we'll place and run the scripts we're about to write in the upcoming sections. To create it, run the following command:

touch /usr/local/bin/demo.sh

Note: While you're free to create the demo.sh file in any directory of your choice, we're placing it in the /usr/local/bin directory for a specific reason. In most Linux distributions, this directory is included in the system's command path. This means we can run our script without making it executable.

With the script file created, let’s move on to the next section.

Parsing Command Line Options Using Positional Parameters

Before we dive into the intricacies of getopts, let's first understand what positional parameters are and how they are typically used to parse command line options and arguments.

Positional parameters are the variables that Bash assigns to inputs we provide to scripts and functions. These parameters serve as placeholders that scripts can reference to process user inputs.

In Bash scripting, positional parameters follow a strict order: $0 represents the name of the script itself, $1 stands for the first argument passed to the script, $2 for the second, and so forth.

Let’s consider an example. Run the following command to open the demo.sh file using the "nano" text editor:

nano /usr/local/bin/demo.sh

Running this command brings up the "nano" text editor in your terminal.

Next, add the following script to the editor:

#!/bin/bash

# Script to display different greeting messages based on user input

case $1 in
  morning)
    echo "Good Morning, $2!"
    ;;
  evening)
    echo "Good Evening, $2!"
    ;;
  *)
    echo "Hello, $2!"
    ;;
esac

In this script, $1 and $2 are positional parameters. When we pass arguments to our script at the command line, these positional parameters allow our script to use these arguments. In the context of our script, $1 is expected to be a time of day (either "morning" or "evening"), and $2 is expected to be a name.

Once you've added the script, you'll need to save your work and exit the "nano" editor.

To save, press ctrl + o. You'll see a message asking for the File Name to Write. Just press enter to agree with the name that's already there. After your file is saved, you can leave "nano" by pressing ctrl + x.

To see this script in action, run it from your command line with the following format:

bash demo.sh <time_of_day> <name>

For example, try running:

bash demo.sh morning Jake

You should see the output: Good Morning, Jake!, as shown below:

Now, let's see what happens if we forget to provide the time of day or name. Try running the script without any arguments:

bash demo.sh

You see an output similar to this:

You'll notice that the script doesn't handle this situation gracefully. It merely prints Hello, !, which is not very helpful. The script isn't aware that the name argument is missing.

Also, try swapping the order of arguments:

bash demo.sh Bruce evening

You'll find that the script doesn't work as expected. It outputs Hello, evening!—which is obviously not the intended result. This is because the script assumes that the first argument is the time of day and the second argument is the name. If the arguments are provided in the wrong order, the script doesn’t have a mechanism to detect this error.

From these examples, you can clearly see some of the limitations of positional parameters. The script lacks the capability to provide meaningful error messages or warnings when required arguments are missing or misused. These limitations can become even more pronounced as the number of parameters a script is required to handle increases. Fortunately, there's a solution to this issue, and that's where getopts comes into the picture.

What is getopts?

getopts is a command that comes built-in with Bash. It is designed to help manage the command line options and arguments that are provided to a script or function. In other words, it aids in 'decoding' what the user has input and determining what actions the script should take.

Compared to positional parameters, getopts is generally more reliable and less prone to errors. This is because getopts allows options and arguments to be clearly labeled and defined, reducing confusion and mistakes that can arise from the strict order requirement of positional parameters.

Before we dive further into getopts, it's important to familiarize ourselves with two terms we'll frequently come across in this post: options and arguments.

  • Options, often referred to as flags, are special kinds of positional parameters that begin with a hyphen (-) and modify the behavior of a command or a script. They are typically single letters, like -v or -h.
  • Arguments, sometimes also called parameters, are positional parameters that provide specific data to the script or command. They might follow an option if that option requires further information, or they could stand alone as inputs to the command or script itself.

For example, in the command ls -l /home, -l is an option that modifies the behavior of the ls command, and /home is an argument that provides specific data (the directory to list) to the ls command.

The getopts Syntax

The getopts command has a relatively simple syntax:

getopts optstring name [args]

Here's a breakdown of the syntax components:

  • optstring: This is a string of characters that are recognized as valid options. Each character in the string represents an option that the script expects to receive. If a character is followed by a colon, it indicates that this specific option requires an argument. For example, an optstring of abc would mean the script is looking for options -a, -b, and -c without any arguments. However, if the optstring is a:bc, it means the script is looking for options -a, -b, and -c, but only -a requires an argument.
  • name: This is a variable that getopts uses to store the current option that it is processing. While this variable can technically be any name you want, it's conventionally named opt or option.
  • [args]: These are optional. If provided, getopts will parse these arguments instead of the positional parameters.

In a typical use case, getopts is used within a while loop to process all the options given to the script. The case statement is often used to perform different actions based on the current option that getopts is processing.

Using getopts: Examples

Let's dive into some examples to see how getopts works in action inside scripts.

Example 1: Handling Multiple Options (No Arguments) with getopts

In this example, we explore a straightforward use of getopts, dealing with two distinct options: -a and -b. This script will demonstrate how to define and handle these options, and also how to manage unexpected inputs, terminating the script with an error status of 1 should an invalid option be encountered.

Run the following command to open the demo.sh file using the "nano" text editor:

nano /usr/local/bin/demo.sh

Next, add the following script to the editor:

#!/bin/bash

OPTSTRING=":ab"

while getopts ${OPTSTRING} opt; do
  case ${opt} in
    a)
      echo "Option -a was triggered."
      ;;
    b)
      echo "Option -b was triggered."
      ;;
    ?)
      echo "Invalid option: -${OPTARG}."
      exit 1
      ;;
  esac
done

Let’s break down this script:

OPTSTRING is defined as :ab. This tells the script to expect two options, -a and -b. Neither of these options requires an argument.

The while getopts ${OPTSTRING} opt; do ... done loop is where getopts does its magic. It will sequentially parse the options passed to the script. For each option it encounters, getopts assigns the option to the opt variable and then executes the body of the loop.

Inside the loop, we have a case ${opt} in ... esac statement, which is responsible for handling each option. If the option is a, it echoes Option -a was triggered.. If the option is b, it echoes Option -b was triggered..

Here's an important piece: the ?) in the case statement. In the context of getopts, a question mark (?) is a special character that is matched when an invalid option is passed (i.e., anything other than "a" or "b" in this script).

When such an invalid option is encountered, getopts assigns that option to the OPTARG variable. After the invalid option is assigned to OPTARG, the Invalid option: -${OPTARG}. message is printed. The script then terminates using exit 1, signaling that an error occurred. The beauty of this is the script not only terminates upon encountering an invalid option, but it also informs the user about the exact invalid option they passed.

After you've written the script in the "nano" editor, save your work by pressing ctrl+o. When prompted, confirm the filename by pressing enter. Finally, press ctrl+x to exit the editor.

Now, run the following command:

bash demo.sh -a

This will output Option -a was triggered. because we passed the -a option.

Now, let's test the script with an unexpected option:

bash demo.sh -c

This will output Invalid option: -c. and terminate the script, as shown below:

As -c is not a valid option in our script, getopts assigns -c to the OPTARG variable, and the case statement with ? gets executed.

As you can see, getopts provides a robust and efficient way of handling command-line options and ensures that invalid inputs are promptly detected and appropriately dealt with. In the next example, we'll explore how getopts can handle options that require arguments.

Example 2: Handling multiple options (with arguments) with getopts

For this example, we'll again handle two options -x and -y, but this time both options require an argument. If an argument is not provided to any of these options or an invalid option is passed, we'll indicate an error and terminate the script with an exit status of 1.

Run the following command to open the demo.sh file using the "nano" text editor:

nano /usr/local/bin/demo.sh

Next, add the following script to the editor:

#!/bin/bash

OPTSTRING=":x:y:"

while getopts ${OPTSTRING} opt; do
  case ${opt} in
    x)
      echo "Option -x was triggered, Argument: ${OPTARG}"
      ;;
    y)
      echo "Option -y was triggered, Argument: ${OPTARG}"
      ;;
    :)
      echo "Option -${OPTARG} requires an argument."
      exit 1
      ;;
    ?)
      echo "Invalid option: -${OPTARG}."
      exit 1
      ;;
  esac
done

Let’s examine the script in detail.

The OPTSTRING is defined as :x:y:. This tells the script to expect two options, -x and -y, both of which require an argument.

The while getopts ${OPTSTRING} opt; do ... done loop sequentially parses the options passed to the script. For each option it encounters, getopts assigns the option to the opt variable and then executes the body of the loop.

Inside the loop, a case ${opt} in ... esac statement handles each option. If the option is x, it echoes Option -x was triggered, Argument: ${OPTARG}. If the option is y, it echoes Option -y was triggered, Argument: ${OPTARG}. Here, ${OPTARG} is a special variable that getopts sets to the argument of the option it's currently processing.

Next, we have the :) in the case statement. This is a special character that is matched when an option that is expected to have an argument is passed without one. When this happens, getopts assigns the option to the OPTARG variable and the Option -${OPTARG} requires an argument. message is printed.

Like in the previous example, a question mark (?) is used to match any invalid option that is passed. In such cases, the invalid option is assigned to the OPTARG variable, and the Invalid option: -${OPTARG}. message is printed.

After printing the appropriate error message in the cases of :) and ?), the script terminates using exit 1, signaling that an error occurred.

Once you've written the script in the "nano" editor, save your work by pressing ctrl+o. When prompted, confirm the filename by pressing enter. Finally, press ctrl+x to exit the editor.

Now, run the following command:

bash demo.sh -x hello

You’ll get the following output:

The output indicates that the -x option was triggered with the argument hello.

Next, let's try the -y option:

bash demo.sh -y world

You’ll get the following output:

The script correctly identifies that the -y option was triggered with the argument world.

Now, let's try passing an option without its required argument:

bash demo.sh -x

This command will result in an error message Option -x requires an argument., as shown below:

getopts correctly identifies that the -x option was passed without an argument, and so it prints out an appropriate error message.

Lastly, let's pass an invalid option:

bash demo.sh -z

As -z is not an option expected by the script, the command will output the error message Invalid option: -z., as shown below:

Here, getopts caught the invalid option -z and displayed an error message. This shows how getopts can handle unexpected situations in a graceful and user-friendly manner.

This dynamic error handling is one of the features that sets getopts apart from positional parameters. With positional parameters, you would have to manually code this kind of error checking into your script, making your code more complex and harder to maintain. getopts simplifies this process, making your scripts more robust and your code cleaner.

Conclusion

In this blog post, we learned what the getopts command is and how it overcomes some of the limitations of positional parameters. Then, we learned how to use the getopts command inside scripts.

Looking to take your existing skills to the next level? Check out this course from KodeKloud:

  • Advanced Bash Scripting: In this course, you'll start with fundamentals like variables, functions, and parameter expansions and then dive deeper into streams, input/output redirection, and command-line utilities like awk and sed. You'll master arrays for data manipulation and storage and learn best practices to create robust scripts.