Tutorials

Bash Tutorial: getopts

Key Takeaways

  • Bash’s built-in getopts parses short options without an external parsing program.
  • A colon after an option letter makes its argument required: a:b means -a takes a value and -b is a flag.
  • A leading colon enables silent error reporting: handle \?) for unknown options and :) for missing option arguments.
  • Parsing stops at the first non-option operand or an end-of-options marker. After the loop, use shift "$((OPTIND - 1))" to expose the remaining operands.

getopts is Bash’s built-in parser for short command-line options such as -a hello and -b. Put it in a while loop, describe your options in an option string, and handle each result with case. Its core interface is specified by POSIX, making it a practical starting point for small shell scripts.

The important limitation is operand placement: getopts does not rearrange your arguments to find options wherever they appear. You’ll build a parser that handles errors deliberately and leaves positional operands—inputs such as filenames—ready for the rest of your script.

Basic getopts Example: An Option Without an Argument

Save this as sample.sh:

#!/usr/bin/env bash

while getopts "a" opt; do
  case "$opt" in
    a) printf '%s\n' 'argument -a called' ;;
    \?) exit 2 ;;
  esac
done

Run it with Bash:

bash sample.sh -a

Output:

argument -a called

The option string "a" recognizes -a without an argument. Each successful parsing iteration assigns an option character to opt; the case statement selects its handler. When parsing finishes, getopts returns a nonzero status and the while loop stops.

For background on running scripts directly rather than through bash sample.sh, see Linux users, groups, and permissions.

Making an Option Require an Argument

Change "a" to "a:". The colon belongs to the preceding option letter, so -a now requires one argument. Bash places that argument in OPTARG.

#!/usr/bin/env bash

while getopts "a:" opt; do
  case "$opt" in
    a) printf 'argument -a called with parameter %s\n' "$OPTARG" ;;
    \?) exit 2 ;;
  esac
done
bash sample.sh -a hello

Output:

argument -a called with parameter hello

Quote values containing spaces at the call site, as in bash sample.sh -a "hello world". Keep "$OPTARG" quoted when passing it to commands so its contents aren’t split into separate words.

Understanding Default Errors

Without a leading colon, Bash normally prints a diagnostic for an unknown option or a missing required argument. In both cases it assigns ? to your option variable and unsets OPTARG.

For example, running the preceding script with bash sample.sh -a can produce a diagnostic resembling:

sample.sh: option requires an argument -- a

Treat that as an example, not a portable output string to match in tests. The diagnostic format is not specified exactly. The sample exits with status 2 because its error handler explicitly calls exit 2.

Custom getopts Error Handling With a Leading Colon

Start the option string with a colon to suppress the parser’s diagnostics and distinguish the two input errors yourself. With getopts ":a:" opt, the first colon selects silent reporting; the colon after a still makes its argument required.

Input errorValue of optValue of OPTARG in silent mode
Unknown option, such as -z?The unknown option letter: z
Missing argument for -a:The option missing its argument: a

These are separate cases: a missing argument does not enter the unknown-option handler.

#!/usr/bin/env bash

while getopts ":a:" opt; do
  case "$opt" in
    a)
      printf 'argument -a called with parameter %s\n' "$OPTARG"
      ;;
    :)
      printf 'Option -%s requires an argument.\n' "$OPTARG" >&2
      exit 2
      ;;
    \?)
      printf 'Invalid option: -%s\n' "$OPTARG" >&2
      exit 2
      ;;
  esac
done

In this script, bash sample.sh -a prints Option -a requires an argument., while bash sample.sh -z prints Invalid option: -z. Both messages go to standard error, and both paths exit with status 2.

The backslash in \?) makes the question mark literal rather than a pattern matching any single character. You can also write '?').

Handling Multiple Arguments: A Complete Bash getopts Example

Here’s the useful distinction from the earlier examples: -a takes a value, but -b does not. This complete script adds help, usage information, explicit failures, and support for any remaining positional operands.

Save this version as sample.sh:

#!/usr/bin/env bash

usage() {
  printf 'Usage: %s [-a value] [-b] [-h] [--] [operand ...]\n' "${0##*/}"
  printf '%s\n' \
    '  -a value  Set a value' \
    '  -b        Enable the b flag' \
    '  -h        Show help'
}

fail() {
  printf '%s\n' "$1" >&2
  usage >&2
  exit 2
}

a_value=
a_supplied=false
b_enabled=false
OPTIND=1

while getopts ":a:bh" opt; do
  case "$opt" in
    a)
      a_value=$OPTARG
      a_supplied=true
      ;;
    b)
      b_enabled=true
      ;;
    h)
      usage
      exit 0
      ;;
    :)
      fail "Option -$OPTARG requires an argument."
      ;;
    \?)
      fail "Invalid option: -$OPTARG"
      ;;
  esac
done

shift "$((OPTIND - 1))"

if [ "$a_supplied" = true ]; then
  printf 'argument -a called with parameter %s\n' "$a_value"
fi

if [ "$b_enabled" = true ]; then
  printf '%s\n' 'argument -b called'
fi

for operand in "$@"; do
  printf 'operand: %s\n' "$operand"
done

exit 0

Read ":a:bh" as four instructions:

  • The initial : enables silent error reporting.
  • a: recognizes -a with a required argument.
  • b recognizes the argument-free -b flag.
  • h recognizes the argument-free -h flag.

Try the original two-option combination, now with two operands:

bash sample.sh -a hello -b first.txt "second file.txt"
argument -a called with parameter hello
argument -b called
operand: first.txt
operand: second file.txt

The script records settings during parsing and prints them afterward. Its a_supplied variable distinguishes an omitted -a from an explicitly supplied empty value; repeating -a replaces the previously recorded value.

OPTIND and Remaining Positional Arguments

OPTIND tracks the next argument to process. After the loop finishes, it identifies the first remaining operand, or one past the argument list if none remain.

For -a hello -b first.txt "second file.txt", the options and their values occupy the first three positional parameters. At the end of parsing, OPTIND is 4, so this removes those three parameters:

# Fragment: run after the getopts loop.
shift "$((OPTIND - 1))"

shift renumbers the remaining positional parameters. Afterward, $1 is first.txt, and "$@" contains the two operands as separate arguments. Use for operand in "$@" to preserve each argument’s boundaries, including spaces.

Bash initializes OPTIND to 1 when starting a shell or script. Before a separate parsing pass in the same shell, reset it with OPTIND=1; don’t reset it inside the parsing loop.

Option Order, Clusters, and the -- Marker

Put Options Before Positional Operands

getopts processes options in the order supplied and normally stops at the first non-option operand. It does not reorder options and operands.

bash sample.sh first.txt -b

Here, parsing stops at first.txt. The complete script treats both first.txt and -b as operands; it does not enable the b flag.

Use a standalone -- to end option parsing explicitly, including when a filename begins with a hyphen:

bash sample.sh -b -- -report.txt

In this invocation, -b is a flag and -report.txt is an operand. The -- marker is consumed during parsing and removed along with the options by the subsequent shift.

Cluster Short Options Without Changing Their Meaning

When both a and b are flags, -ab groups them. An option requiring an argument changes that interpretation: following characters in the same token become its argument.

With this script’s a: rule, -ab supplies b as the value of -a; it does not enable the -b flag. These invocations instead enable -b and supply hello to -a:

bash sample.sh -ba hello
bash sample.sh -bahello

Bash getopts Versus getopt

Prefer getopts when your interface needs short options and you want to avoid an external parsing dependency. Choose another approach when long options are part of the interface you need.

ConsiderationBash getoptsExternal getopt
AvailabilityBuilt into Bash; its core interface is specified by POSIX.A separate command whose implementation varies by platform.
Long optionsNo native long-option interface.Enhanced implementations, including util-linux, support long options.
IntegrationUpdates shell variables directly.The util-linux implementation produces parsed output for the shell to consume.

The distinction isn’t that every getopt implementation is broken or unsuitable. The util-linux getopt manual documents enhanced behavior and compatibility considerations. Check the implementation on your target systems before depending on its extensions.

Common Mistakes

  • Handling only \?) in silent mode. Include a separate :) handler for missing arguments.
  • Printing an error but continuing execution. Give invalid input an explicit failure path before the script performs its real work.
  • Shifting inside the parsing loop. Leave the argument list intact while getopts tracks its position, then shift once after the loop.
  • Using "$*" to iterate over operands. It joins the positional parameters into one word. Use "$@" to keep them separate.
  • Confusing syntax validation with application validation. After parsing, check required settings, allowed values, and operand counts before doing work.

Keep regression cases for no options, valid flags, unknown options, a missing value, a quoted value containing spaces, clustered options, and --. Assert your own diagnostics and exit statuses rather than Bash’s diagnostic wording. The same reasoning behind the benefits of unit testing applies to these small command-line interfaces.

FAQ

How do I use getopts in Bash?

Call getopts in a while loop and handle the returned option character with case. In the option string, a letter without a following colon represents a flag; a letter followed by a colon requires an argument.

How do I detect a missing option argument with getopts?

Start the option string with a colon to enable silent error reporting. A missing required argument sets your option variable to : and OPTARG to the affected option letter; an unknown option instead sets the option variable to ?.

How do I handle positional arguments after getopts?

After the loop, run shift "$((OPTIND - 1))" to remove the parsed options and their arguments. OPTIND identifies where parsing stopped, while OPTARG holds an option’s argument when one is parsed; iterate over the remaining operands with for operand in "$@".

What is the difference between getopts and getopt?

getopts is built into Bash and has a POSIX-specified core interface. getopt is an external command whose implementations differ; enhanced versions such as util-linux getopt support long options, so choose based on your interface and target systems.

Can Bash getopts parse long options?

Bash getopts does not natively provide long-option parsing for names such as --help. Use short options such as -h, implement a separate parser, or choose an enhanced getopt implementation available on your target systems.

Further Reading

Written by Sam Erickson

Software engineer. I write every article on StackChief myself. More about me