Thuta Learning
AdvancedDevOps & Toolsbeginner

Parsing Command-Line Flags with getopts

What you'll walk away with

  • Explain the core ideas behind Parsing Command-Line Flags with getopts
  • Run the sample code and verify its output
  • Apply the technique correctly to the Tutorial Platform and production scenarios

Build the mental model

Parsing arguments by hand with a chain of if [ "$1" = "-v" ]; then ...; shift checks works for the simplest cases, but gets unreliable fast once flags can take values, appear in any order, or get combined (-vo report.txt versus -v -o report.txt). getopts is bash's built-in loop for doing this correctly: while getopts ":vo:h" opt; do case $opt in ... esac; done walks through the positional parameters one flag at a time, using the option string to know how to treat each letter — v and h are standalone flags, while the colon immediately after o in "vo:h" tells getopts that -o consumes the next argument as its value rather than being a flag on its own. Inside the loop, $opt holds the current flag letter on each iteration, $OPTARG holds that flag's value when it takes one, and $OPTIND is an index getopts increments internally to track how far into the argument list it has already consumed. Once every flag has been consumed, shift $((OPTIND - 1)) discards them all at once, leaving $1, $2, and so on as the real positional arguments — filenames, usually — that followed the flags. Two things worth knowing going in: getopts only understands short, single-letter flags like -v, not long GNU-style flags like --verbose (parsing those needs the separate getopt utility or hand-rolled logic), and OPTIND is not automatically reset between separate getopts loops run in the same shell session, so a script or test suite that runs the parsing loop more than once needs to reset OPTIND=1 first, or the second call silently picks up wherever the first one left off.

Connect it to a real scenario

A backup script needs to support ./backup.sh -v -o /mnt/backups file1.txt file2.txt: a verbose flag, an output directory that takes a value, and then a variable number of files to actually back up. Hand-rolled parsing with a chain of if-statements breaks the moment someone passes the flags in a different order, combines -v and -o, or puts the output path right after -o without a space; getopts handles every one of those standard conventions correctly without any extra code from you. Walking through the example: while getopts ":vo:h" opt does one iteration per flag, setting opt to v on the first pass (so the case statement sets verbose=1), then to o on the second pass, where $OPTARG is automatically set to report.txt so output=$OPTARG captures it. Once getopts has consumed both flags and their value, OPTIND points past them, so shift $((OPTIND - 1)) removes -v -o report.txt from the argument list entirely, leaving only somefile.txt anotherfile.txt behind in $1 and $2 — exactly the files the script actually needs to back up, regardless of how many flags came before them or in what order.

Try the working example

bash
#!/bin/bash
set -euo pipefail

verbose=0
output="default.txt"

usage() {
  echo "Usage: script.sh [-v] [-o file] [-h]"
}

# Simulate command-line arguments for this example
set -- -v -o report.txt somefile.txt anotherfile.txt

while getopts ":vo:h" opt; do
  case $opt in
    v)
      verbose=1
      ;;
    o)
      output=$OPTARG
      ;;
    h)
      usage
      exit 0
      ;;
    \?)
      echo "Unknown option: -$OPTARG" >&2
      usage
      exit 1
      ;;
    :)
      echo "Option -$OPTARG requires an argument" >&2
      exit 1
      ;;
  esac
done
shift $((OPTIND - 1))

echo "verbose=$verbose"
echo "output=$output"
echo "remaining positional args: $*"
You should see
Running the script above prints:

verbose=1
output=report.txt
remaining positional args: somefile.txt anotherfile.txt

5-minute try-it

Extend the getopts loop to support a new -q (quiet) flag alongside the existing ones, and add a case that prints an error and exits if the same script is called with no flags at all.

One important caution

Forgetting the leading colon in the option string (":vo:h" vs "vo:h") — without it, getopts prints its own error messages for invalid options instead of letting your script handle them via the \? case

Forgetting shift $((OPTIND - 1)) after the loop, so $1 still refers to the first flag instead of the first real positional argument

GNU Bash Manual — Bash Builtins (getopts)Bash / Shell Scripting

Easy traps

  • Forgetting the leading colon in the option string (":vo:h" vs "vo:h") — without it, getopts prints its own error messages for invalid options instead of letting your script handle them via the \? case
  • Forgetting shift $((OPTIND - 1)) after the loop, so $1 still refers to the first flag instead of the first real positional argument
  • Validate sample code in a local or test environment before applying it to a production system.

Exercise

Extend the getopts loop to support a new -q (quiet) flag alongside the existing ones, and add a case that prints an error and exits if the same script is called with no flags at all.

You'll know it worked when: Running the script above prints: verbose=1 output=report.txt remaining positional args: somefile.txt anotherfile.txt

Parsing Command-Line Flags with getopts | Thuta Learning