Bash getopts Tutorial: Syntax, Examples, Options & Error Handling

Bash getopts Tutorial: Syntax, Examples, Options & Error Handling

bash getopts is a built-in utility that helps you parse command-line options and arguments in a structured and reliable way. It allows scripts to handle flags, options with values, and error conditions efficiently. With getopts, you can build user-friendly and robust shell scripts without relying on external tools.

This tutorial explains how to use getopts in bash scripts with examples, including handling arguments, OPTARG usage, and comparison with getopt.


How getopts Works in Bash

When you run a Bash script, any values you pass (like -f file.txt) are available as positional parameters ($1, $2, etc.). Manually parsing these becomes messy as the script grows.

getopts provides a structured way to read these options one by one.

In this example, we will:

  • accept a file using -f
  • enable verbose mode using -v
bash
#!/bin/bash

while getopts "f:v" opt; do
  case $opt in
    f)
      echo "File: $OPTARG"
      ;;
    v)
      echo "Verbose mode enabled"
      ;;
  esac
done

Run:

bash
./script.sh -f test.txt -v

Output:

bash
File: test.txt
Verbose mode enabled

What is happening here:

  • "f:v" defines which options are allowed
  • f: means -f must have a value
  • v means -v is just a flag
  • opt stores the current option (f or v)
  • OPTARG stores the value (test.txt)

getopts processes each option in order and runs the matching block inside case.


Passing Arguments Without Value

In many scripts, you need input from the user, such as a filename, directory, or username.

For this, options must accept a value.

In getopts, this is done using a colon (:).

When you add : after an option, it means that option requires a value.

In this example, we will read a filename using -f.

bash
#!/bin/bash

while getopts "f:" opt; do
  case $opt in
    f)
      file="$OPTARG"
      echo "File provided: $file"
      ;;
  esac
done

Run:

bash
./script.sh -f file.txt

Output:

bash
File provided: file.txt

Here:

  • f: means -f must be followed by a value
  • file.txt is provided by the user
  • OPTARG stores that value
  • file="$OPTARG" assigns it to a variable

You can define multiple options with values.

bash
#!/bin/bash

while getopts "f:d:" opt; do
  case $opt in
    f)
      file="$OPTARG"
      ;;
    d)
      dir="$OPTARG"
      ;;
  esac
done

echo "File: $file"
echo "Directory: $dir"

Run:

bash
./script.sh -f file.txt -d /tmp

Output:

bash
File: file.txt
Directory: /tmp

If a value is missing:

bash
./script.sh -f

The script will not automatically stop. You must handle this case explicitly (covered in the missing argument section).

In practice, this pattern is used for:

  • file input → -f file.txt
  • directory input → -d /tmp
  • username → -u admin

You can choose any option letters based on your script design.


Passing Arguments With Value

Some options need additional input from the user, such as a filename or directory.

In this example, we will pass a filename using -f.

bash
#!/bin/bash

while getopts "f:" opt; do
  case $opt in
    f)
      file="$OPTARG"
      echo "File provided: $file"
      ;;
  esac
done

Run:

bash
./script.sh -f file.txt

Output:

bash
File provided: file.txt

Here:

  • f: means -f requires a value
  • file.txt is passed by the user
  • OPTARG stores that value

If the value is missing:

bash
./script.sh -f

The script will not behave correctly unless you handle this case (covered later).


Mandatory and Optional Arguments

In real scripts, some inputs are required while others are optional.

In this example, we will:

  • require a file (-f) → mandatory
  • allow verbose mode (-v) → optional
bash
#!/bin/bash

file=""
verbose=false

while getopts "f:v" opt; do
  case $opt in
    f)
      file="$OPTARG"
      ;;
    v)
      verbose=true
      ;;
  esac
done

if [ -z "$file" ]; then
  echo "Error: -f <file> is required"
  exit 1
fi

echo "File: $file"
[ "$verbose" = true ] && echo "Verbose mode enabled"

Run:

bash
./script.sh -f test.txt

Output:

bash
File: test.txt

If mandatory argument is missing:

bash
./script.sh -v

Output:

bash
Error: -f <file> is required

Here:

  • mandatory arguments are validated after parsing
  • optional arguments simply change behavior if present

Handling Invalid Options

Users may pass options that your script does not support.

In this example, we will handle invalid options.

bash
#!/bin/bash

while getopts "f:" opt; do
  case $opt in
    f)
      echo "File: $OPTARG"
      ;;
    \?)
      echo "Invalid option: -$OPTARG"
      exit 1
      ;;
  esac
done

Run:

bash
./script.sh -x

Output:

bash
Invalid option: -x

Here:

  • \? catches unsupported options
  • $OPTARG contains the invalid option
  • You can stop execution using exit 1

This ensures your script fails safely and clearly.


Handling Missing Argument

Sometimes users may provide an option but forget to pass its value.

Example:

bash
./script.sh -f

Here, -f expects a value, but none is provided.

By default, getopts will:

  • print an error message
  • set the option to ?

To handle this properly, you can use a leading colon (:) in the option string and manage the error yourself.

bash
#!/bin/bash

while getopts ":f:" opt; do
  case $opt in
    f)
      echo "File: $OPTARG"
      ;;
    :)
      echo "Error: Option -$OPTARG requires a value"
      exit 1
      ;;
    \?)
      echo "Invalid option: -$OPTARG"
      exit 1
      ;;
  esac
done

Run:

bash
./script.sh -f

Output:

bash
Error: Option -f requires a value

Leading Colon Behavior in getopts

Adding a colon (:) at the beginning of the option string changes how errors are handled.

Example:

bash
while getopts ":f:" opt; do

This enables silent mode, meaning:

  • getopts does not print default error messages
  • you handle errors manually inside case

Behavior difference:

Without leading ::

  • invalid option → opt = ?
  • missing value → opt = ?
  • Bash prints error automatically

With leading ::

  • invalid option → opt = ?
  • missing value → opt = :
  • no automatic error message

Example:

bash
#!/bin/bash

while getopts ":f:" opt; do
  case $opt in
    f)
      echo "File: $OPTARG"
      ;;
    :)
      echo "Missing value for -$OPTARG"
      ;;
    \?)
      echo "Invalid option: -$OPTARG"
      ;;
  esac
done

Using Positional Arguments After getopts

In many scripts, you don’t just pass options — you also pass additional inputs.

Example:

bash
./script.sh -f file.txt input1 input2

Here:

  • -f file.txt → option handled by getopts
  • input1 input2 → extra inputs (called positional arguments)

Why do we need shift $((OPTIND-1))?

When getopts finishes parsing options, it keeps track of how many arguments it has already processed using a variable called OPTIND.

If you directly access $@ without shifting, it will still include the options.

To remove already-processed options, we use:

bash
shift $((OPTIND-1))

This shifts the arguments so only remaining inputs are left.

Example without shift (incorrect behavior)

bash
#!/bin/bash

while getopts "f:" opt; do
  case $opt in
    f)
      file="$OPTARG"
      ;;
  esac
done

echo "All args: $@"

Run:

bash
./script.sh -f file.txt input1 input2

Output:

bash
All args: -f file.txt input1 input2

Here, options are still mixed with inputs.

Example with shift (correct behavior)

bash
#!/bin/bash

while getopts "f:" opt; do
  case $opt in
    f)
      file="$OPTARG"
      ;;
  esac
done

shift $((OPTIND-1))

echo "File: $file"
echo "Remaining args: $@"

Run:

bash
./script.sh -f file.txt input1 input2

Output:

bash
File: file.txt
Remaining args: input1 input2

After shifting:

  • $1 → first positional argument
  • $2 → second positional argument

Example:

bash
echo "First input: $1"
echo "Second input: $2"

You can process all remaining inputs using a loop:

bash
for arg in "$@"; do
  echo "Processing: $arg"
done

This pattern is commonly used when:

  • options define behavior → -v, -f
  • positional arguments define targets → files, hosts, users

Example:

bash
./backup.sh -v file1.txt file2.txt file3.txt

Here:

  • -v → enables verbose mode
  • remaining arguments → list of files to process

Supporting Long Options in Bash

getopts supports only short options like -f, -v.

It does not support long options like:

bash
--file file.txt

To handle long options, you need a manual approach using case and shift.

Example:

bash
#!/bin/bash

while [[ $# -gt 0 ]]; do
  case "$1" in
    --file)
      file="$2"
      shift 2
      ;;
    --verbose)
      verbose=true
      shift
      ;;
    *)
      echo "Unknown option: $1"
      exit 1
      ;;
  esac
done

echo "File: $file"
[ "$verbose" = true ] && echo "Verbose enabled"

Run:

bash
./script.sh --file file.txt --verbose

getopts vs getopt

Both getopts and getopt are used for parsing arguments, but they work differently.

getopts:

  • built into Bash
  • simple and portable
  • supports short options only

getopt:

  • external command
  • supports long options
  • more flexible but less portable

Example using getopt:

bash
getopt -o f: -l file: -- "$@"

Use:

  • getopts → for simple scripts
  • getopt → when you need long options

Advanced Usage in Bash Scripts

In real-world scripts, you may need more control over argument parsing.

Example: using getopts inside a function

bash
parse_args() {
  local OPTIND opt

  while getopts "f:v" opt; do
    case $opt in
      f)
        file="$OPTARG"
        ;;
      v)
        verbose=true
        ;;
    esac
  done
}

Example: handling repeated options

bash
#!/bin/bash

files=()

while getopts "f:" opt; do
  case $opt in
    f)
      files+=("$OPTARG")
      ;;
  esac
done

echo "Files: ${files[@]}"

Run:

bash
./script.sh -f file1.txt -f file2.txt

Output:

bash
Files: file1.txt file2.txt

Example: combining flags and arguments

bash
./script.sh -v -f file.txt

This allows flexible and powerful script behavior.


Advanced Bash Script Using getopts

This script combines all common real-world use cases:

  • flags without value → -v, -d
  • options with value → -f, -o
  • mandatory argument → at least one file required
  • optional arguments → output file, verbose, debug
  • handling missing values
  • handling invalid options
  • positional arguments support
  • repeated options support
bash
#!/bin/bash

verbose=false
debug=false
output=""
files=()

while getopts ":f:o:vd" opt; do
  case $opt in
    f)
      files+=("$OPTARG")
      ;;
    o)
      output="$OPTARG"
      ;;
    v)
      verbose=true
      ;;
    d)
      debug=true
      ;;
    :)
      echo "Error: Option -$OPTARG requires a value"
      exit 1
      ;;
    \?)
      echo "Invalid option: -$OPTARG"
      exit 1
      ;;
  esac
done

shift $((OPTIND-1))

# Add positional arguments
for arg in "$@"; do
  files+=("$arg")
done

# Validate mandatory input
if [ ${#files[@]} -eq 0 ]; then
  echo "Error: At least one file must be provided"
  exit 1
fi

# Default output
[ -z "$output" ] && output="output.txt"

[ "$debug" = true ] && echo "Debug: Files=${files[*]}, Output=$output"

for file in "${files[@]}"; do
  [ "$verbose" = true ] && echo "Processing $file"
  echo "Processed $file" >> "$output"
done

echo "Output written to $output"

Run examples:

bash
./script.sh -f file1.txt -f file2.txt -o result.txt -v
bash
./script.sh -v file1.txt file2.txt
bash
./script.sh -f file1.txt file2.txt -o result.txt
bash
./script.sh -f
bash
./script.sh -x

Explanation:

  • :f:o:vd → defines all supported options
  • f: and o: → require values
  • v, d → flags
  • files+=("$OPTARG") → supports multiple -f inputs
  • shift $((OPTIND-1)) → removes processed options
  • $@ → remaining positional arguments
  • :) → handles missing values
  • \? → handles invalid options
  • validation ensures at least one file is provided
  • default output file is assigned if not passed

This is how real-world Bash CLI tools are designed using getopts.


Frequently Asked Questions

1. What is bash getopts used for?

Bash getopts is a built-in utility used to parse command-line options and arguments in shell scripts in a structured and reliable way.

2. What is the difference between getopts and getopt?

getopts is a Bash built-in designed for short options, while getopt is an external command that supports both short and long options but may not be available on all systems.

3. What does a leading colon mean in getopts?

A leading colon enables silent error handling, where getopts does not print errors but instead returns specific values like ':' for missing arguments.

4. What is OPTARG in bash getopts?

OPTARG is a variable that stores the argument value associated with an option when using getopts in a shell script.

5. How do I handle missing arguments in getopts?

You can handle missing arguments using ':' in the case statement, which is triggered when an option that requires an argument is used without one.

6. Can getopts handle long options?

No, getopts does not natively support long options, but you can implement workarounds using case statements and manual parsing.

7. Why is my getopts script not parsing options correctly?

Common issues include incorrect option string, missing OPTIND reset, or misuse of OPTARG and case conditions.

8. What is OPTIND in getopts?

OPTIND is a variable that stores the index of the next argument to be processed by getopts.

9. How do I reset getopts in a script?

You can reset getopts by setting OPTIND=1 before reusing it in the same script or function.

Summary

bash getopts is a reliable and portable way to parse command-line options in shell scripts. It supports flags, options with arguments, and structured error handling using variables like OPTARG and OPTIND. While it is limited to short options, it remains the preferred choice for writing clean, maintainable, and user-friendly Bash scripts.

By understanding key concepts such as colon behavior, argument handling, and shifting positional parameters, you can avoid common mistakes and build robust scripts. For more advanced needs like long options, alternative approaches such as getopt or manual parsing can be used.


Official Documentation

Deepak Prasad

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.