6 min read

How to Process Command-Line Arguments and Options in Your Bash Scripts

Table of Contents

Creating a Bash script is powerful, but making it flexible and user-friendly is what separates a good script from a great one. The key is allowing users to pass information to your script right from the command line.

This guide will walk you through the two primary ways to process command-line arguments: the simple method using positional parameters and the more robust, industry-standard method using the getopts built-in command.

1. The Basics: Positional Parameters

The simplest way to read arguments is by using positional parameters. Bash automatically assigns arguments to special variables based on their order.

  • $1: The first argument
  • $2: The second argument
  • …and so on.

Let’s create a simple script to see this in action.

greet.sh

#!/bin/bash

# Check if at least two arguments are provided
if [ "$#" -lt 2 ]; then
    echo "Usage: $0 <greeting> <name>"
    exit 1
fi

GREETING=$1
NAME=$2

echo "$GREETING, $NAME!"

How it works:

  • #!/bin/bash: The shebang, telling the system to use Bash.
  • if [ "$#" -lt 2 ]: This is a check. $# is a special variable that holds the count of arguments. If the count is less than two, the script prints a usage message and exits.
  • $0: Another special variable that holds the name of the script itself.
  • $1 and $2: We assign the first and second arguments to our own variables for clarity.

Make the script executable and run it:

chmod +x greet.sh

./greet.sh "Hello there" "Alice"
# Output: Hello there, Alice!

./greet.sh "Good morning"
# Output: Usage: ./greet.sh <greeting> <name>

Other Useful Special Variables:

  • $@: Expands to all command-line arguments as separate, individually quoted strings ("$1" "$2" "$3" ...). This is the safest way to use all arguments.
  • $*: Expands to all arguments as a single string ("$1 $2 $3 ..."). Use this with caution, as it can cause issues with arguments containing spaces.

Positional parameters are great for simple scripts where the argument order is fixed and obvious. But what if you want optional flags, like -v for verbose output, in any order? That’s when things get complicated and getopts becomes necessary.

2. The Robust Solution: Using getopts

getopts is a Bash built-in command specifically designed to parse options (flags that start with a -) and their arguments. It’s the standard, reliable way to create complex command-line interfaces.

The basic structure of a getopts loop looks like this:

while getopts ":optstring" opt; do
  case $opt in
    # ... handle options here ...
  esac
done

Let’s break down a complete, practical example. Imagine a script that needs an input file, an optional output file, and an optional verbose flag.

process.sh

#!/bin/bash

# --- Default values ---
VERBOSE=false
OUTPUT_FILE=""
# We'll make the input file mandatory later

# --- Help function ---
usage() {
    echo "Usage: $0 -i <input_file> [-o <output_file>] [-v] [-h]"
    echo "  -i <input_file>   Specify the input file (mandatory)."
    echo "  -o <output_file>  Specify the output file (optional)."
    echo "  -v                Enable verbose mode."
    echo "  -h                Display this help message."
    exit 1
}

# --- Parse options ---
while getopts ":i:o:vh" opt; do
  case ${opt} in
    i)
      INPUT_FILE=$OPTARG
      ;;
    o)
      OUTPUT_FILE=$OPTARG
      ;;
    v)
      VERBOSE=true
      ;;
    h)
      usage
      ;;
    \?)
      echo "Invalid option: -$OPTARG" >&2
      usage
      ;;
    :)
      echo "Option -$OPTARG requires an argument." >&2
      usage
      ;;
  esac
done

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

# --- Validation ---
if [ -z "${INPUT_FILE}" ]; then
    echo "Error: Input file is mandatory."
    usage
fi

# --- Main script logic ---
echo "--- Configuration ---"
echo "Input File: ${INPUT_FILE}"
echo "Output File: ${OUTPUT_FILE:-'stdout'}" # Use 'stdout' if OUTPUT_FILE is empty
echo "Verbose Mode: ${VERBOSE}"
echo "Remaining arguments: $@"
echo "---------------------"

if [ "$VERBOSE" = true ]; then
    echo "Processing ${INPUT_FILE} verbosely..."
fi

# Your actual script logic would go here
echo "Processing complete."

Dissecting the getopts Script:

  1. usage() function: Every good script should have a help message. This function prints it and exits.
  2. while getopts ":i:o:vh" opt; do: This is the core loop.
    • The first colon (:) enables silent error handling. It lets us decide how to handle errors instead of getopts printing its own messages.
    • i:o:vh: This is the “option string”.
      • i: and o:: The colon after a letter means that option requires an argument (e.g., -i <file>).
      • v and h: Letters without a colon are simple boolean flags (e.g., -v).
    • opt: The variable that holds the current option letter being processed (e.g., i, o, v).
  3. case ${opt} in: This block handles each option.
    • i): When the -i option is found, its argument is automatically stored in the $OPTARG variable. We assign it to INPUT_FILE.
    • v): When the -v flag is found, we set our VERBOSE variable to true.
    • \?): If an unknown option is used (e.g., -x), getopts (in silent mode) sets opt to ? and OPTARG to the invalid option letter. We print an error and show the usage.
    • :): If an option is missing its required argument (e.g., just -i), getopts sets opt to : and OPTARG to the option letter. We handle that error, too.
  4. shift $((OPTIND - 1)): This is a crucial step! $OPTIND is the index of the next argument to be processed. After the loop, this command removes all the options and their arguments that getopts has already handled, leaving only the “leftover” positional arguments in $@.
  5. Validation: After parsing, we check if mandatory arguments (like INPUT_FILE) were actually provided.

Running the getopts Script:

Make it executable (chmod +x process.sh) and try these commands:

# Get help
./process.sh -h

# Basic usage
./process.sh -i data.log

# With all options and a leftover argument
./process.sh -v -i data.log -o processed.log extra_arg

# Invalid option
./process.sh -x
# Output: Invalid option: -x

# Missing argument
./process.sh -i
# Output: Option -i requires an argument.

Conclusion

You now have the tools to handle any command-line input your script might need.

  • Positional Parameters ($1, $2, $#): Perfect for simple scripts with a fixed number of required arguments. They are easy to use but not very flexible.
  • getopts: The definitive tool for creating robust, standard-compliant command-line interfaces with optional flags and arguments. It makes your scripts feel like professional Linux commands.

Final Tip: Always include a -h or --help option in your scripts using getopts. It’s a small effort that makes your tools significantly more user-friendly for yourself and others.