Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 57

Bash Command Line Arguments and Options

Recommended resources

Hello! Welcome back to your journey into Bash scripting.

In our last lesson, you learned how to build logic into your scripts using control structures like if, for, and while. This gave you the power to make decisions and repeat actions. Now, we're going to make your scripts interactive and far more flexible by allowing them to accept input directly from the command line.

Today's Learning Outcome: By the end of this lesson, you will be able to handle command-line arguments and options in your Bash scripts. This is a crucial skill for creating professional, reusable automation tools.

1. The Basics: Positional Parameters

When you run a command like cp source.txt destination.txt, source.txt and destination.txt are called arguments. Bash makes these arguments available inside your script through special variables called positional parameters.

  • $0: The name of the script itself.
  • $1: The first argument.
  • $2: The second argument.
  • ...and so on ($3, $4, etc.).

Bash also provides a few other special variables that are extremely useful:

  • $#: The total number of arguments passed to the script.
  • $@: All arguments, treated as separate, quoted strings. This is generally the safest way to access all arguments.
  • $*: All arguments, treated as a single string.

The following image provides a clear visual of how these parameters map to a command you'd type in your terminal.

Bash Command Line Argument Handling
This diagram shows how a command and its arguments are assigned to positional parameters (`$0`, `$1`, `$2`, `$3`) and other special variables (`$#`, `$@`, `$*`) within a script.

To see this in action and learn how to check if arguments were provided, the first resource provides a great introduction.

Writing shell scripts - Lesson 12: Positional Parameters

This lesson from linuxcommand.org clearly introduces positional parameters and how to use them to access command-line arguments. It also shows you how to check the number of arguments provided.

Please read the introduction and the sections 'Positional Parameters' and 'Detecting Command Line Arguments'. Pay attention to the simple script examples that demonstrate how $0, $1, and $# work.

Test your understanding!

Write a script named greeting.sh that accepts two arguments: a first name and a last name. The script should then print a message like: "Hello, [FirstName] [LastName]! You provided 2 arguments."

For example, running ./greeting.sh John Doe should output: Hello, John Doe! You provided 2 arguments.

Show answer
#!/bin/bash

# Check if exactly two arguments are given
if [ "$#" -ne 2 ]; then
    echo "Usage: $0 <firstname> <lastname>"
    exit 1
fi

FIRST_NAME=$1
LAST_NAME=$2

echo "Hello, $FIRST_NAME $LAST_NAME! You provided $# arguments."

This script first checks if the number of arguments ($#) is not equal to 2. If it isn't, it prints a usage message and exits. Otherwise, it uses $1 and $2 to construct the greeting.


2. Limitations of Positional Parameters

While simple positional parameters work for basic scripts, they quickly become cumbersome. What if the order of arguments shouldn't matter? What if you want to use optional flags, like ls -l, where -l is an option that modifies the command's behavior? Trying to manage this with just $1, $2, etc., leads to complex and fragile code.

The following article does a great job of demonstrating why we need a more robust method.

How to Use Bash Getopts With Examples

This article from KodeKloud clearly illustrates the shortcomings of relying solely on positional parameters, providing the motivation for learning more advanced parsing techniques.

Read the section 'Limitations of Positional Parameters'. Notice how the example script breaks when arguments are in the wrong order or are missing.


3. A More Flexible Approach: The while Loop and shift

A common pattern for processing more complex arguments is to use a while loop that iterates through them one by one. The key to this pattern is the shift command.

shift moves all positional parameters down by one. $2 becomes $1, $3 becomes $2, and so on. The old $1 is discarded.

By combining while, case (which you've seen before), and shift, you can create a powerful argument-parsing loop. This allows you to handle options (like -f) and their corresponding arguments (like filename.txt).

Let's return to the linuxcommand.org resource to see how this pattern is built.

Writing shell scripts - Lesson 12: Positional Parameters

This part of the lesson explains a standard, powerful pattern for parsing command-line options using a while loop, a case statement, and the shift command.

Please read the sections 'Command Line Options', the explanation of the while loop and shift, and 'Getting an Option's Argument'. Focus on understanding the logic of the while [ "$1" != "" ]; do ... shift; done block.

This while/shift/case pattern is very powerful and gives you full control. It's especially useful because it can handle both short options (-f) and long options (--file), which is common in many Linux tools.


4. The Standard for Simple Options: getopts

For handling standard, single-character options (like -a, -b, -c), Bash provides a built-in command called getopts. It is more robust and less error-prone than manually managing shift for simple cases. It is the POSIX-standard way to parse options.

Here's how it generally works:

  • You define an OPTSTRING that tells getopts which options to expect and which ones require an argument.
  • You use getopts inside a while loop.
  • getopts automatically populates variables for you: one for the option character itself, and OPTARG for the option's argument.
  • It also provides built-in error handling for invalid options or missing arguments.

The KodeKloud article provides an excellent, step-by-step guide to using getopts.

How to Use Bash Getopts With Examples

This guide provides a fantastic breakdown of the getopts command, which is the standard for parsing simple command-line options in Bash.

Read the sections 'Why getopts is preferred...', 'The getopts Syntax & Fundamentals', and the two examples that follow. Pay close attention to: The OPTSTRING syntax (e.g., :ab vs :x:y:). How to handle options that require arguments using OPTARG. How to handle errors using the ? and : cases. Finally, review the 'FAQ' section, especially the questions about the leading colon, $OPTARG, and $OPTIND.

After the getopts loop finishes, any remaining command-line arguments (the non-option ones) are still in the positional parameters. To access them easily, you can use shift $((OPTIND - 1)). This command removes all the options that getopts has processed, leaving $1, $2, etc., pointing to the remaining arguments. This is a very common and important practice.

Test your understanding!

Write a script user_manager.sh that accepts the following:

  1. An option -a (for "add").
  2. An option -d (for "delete").
  3. A username as a non-option argument.

The script should print a different message depending on the option.

  • ./user_manager.sh -a jdoe should print: Adding user: jdoe
  • ./user_manager.sh -d jdoe should print: Deleting user: jdoe
  • If no option is given or an invalid one is used, it should print a usage message.
Show answer
#!/bin/bash

# Default action is empty
ACTION=""

# Use getopts to parse options
while getopts "ad" opt; do
  case $opt in
    a)
      ACTION="add"
      ;;
    d)
      ACTION="delete"
      ;;
    \?) # Handle invalid options
      echo "Invalid option: -$OPTARG" >&2
      exit 1
      ;;
  esac
done

# Shift away the processed options
shift $((OPTIND-1))

# Check if a username was provided
if [ -z "$1" ]; then
    echo "Usage: $0 [-a|-d] <username>"
    exit 1
fi

USERNAME=$1

# Perform action based on the option found
if [ "$ACTION" == "add" ]; then
    echo "Adding user: $USERNAME"
elif [ "$ACTION" == "delete" ]; then
    echo "Deleting user: $USERNAME"
else
    echo "Usage: $0 [-a|-d] <username>"
    exit 1
fi

This script uses getopts to determine the action. It then uses shift to get to the username argument. Finally, it uses an if statement to perform the correct action.


Conclusion

You've now learned how to create flexible and powerful Bash scripts that can be configured at runtime. This is a fundamental skill for any DevOps engineer, as it allows you to write one script that can be adapted to many situations instead of writing many different scripts.

Key Takeaways:

  • Positional Parameters ($1, $2, $#): Simple and effective for scripts that always take a fixed number of arguments in a specific order.
  • The while/shift Pattern: A flexible, manual way to parse arguments. It's particularly useful for handling non-standard or long options (--verbose).
  • getopts: The Bash built-in standard for parsing simple, single-character options (e.g., -f, -v). It provides robust error handling and is the preferred method for creating POSIX-compliant scripts.
  • shift $((OPTIND-1)): The essential command to run after a getopts loop to access any remaining non-option arguments.

In our next lesson, we'll dive into another core DevOps skill: text manipulation. You will learn how to perform text substitution and filtering on data streams using sed, a powerful tool for transforming text and configuration files programmatically.