Bash error: illegal option

getopts prints "illegal option" for a flag missing from the option string. Declare the flag, or use silent mode to handle unknown flags gracefully.

Error String

bash: illegal option -- x

Tldr

A flag was passed that getopts wasn't told about. Add it to the option string, or use silent mode (leading `:`) to catch unknown flags in your own case branch.

Cause

Usually a typo on the command line, or a copy-pasted invocation from a different version of the script. Without explicit handling, getopts prints the platform-default error and continues.

Repro

while getopts "ab" opt; do :; done
# ./script.sh -x
# bash: illegal option -- x

Fix

while getopts ":ab" opt; do
  case $opt in
    a|b) ;;
    \?) echo "unknown flag -$OPTARG; try -h for help" >&2; exit 64 ;;
  esac
done

Explanation

Silent mode also suppresses the default error message and gives you control over wording and exit code — important for scripts shipped to teams.

Faq

Q

Can getopts handle --long-options?

A

No. Use `getopt(1)` from util-linux, or parse `$1` manually in a `while` loop with a `case` on the full word.

Q

Why are my flags ignored after a filename?

A

`getopts` stops at the first non-option argument, so anything after it is positional.

Deep Dive

Heading

The option string drives everything

Body

In `getopts "ab:c" opt`, each letter is an accepted flag and a following colon means that flag takes an argument. Any flag not in the string produces the illegal-option message and sets `opt` to `?`. A leading colon — `getopts ":ab:c" opt` — switches to silent error reporting, letting you print your own usage text instead of the built-in one. Long options such as `--verbose` are not supported by `getopts` at all; they arrive as the single character `-` followed by the rest.

Heading

Shifting and the argument order

Body

After the parse loop you must run `shift $((OPTIND - 1))` so that `$1` becomes the first non-option argument. Forgetting it makes the script reprocess flags as positional parameters. `getopts` also stops at the first non-option word, so `script file.txt -v` never sees `-v`; document that flags come first, or reorder arguments yourself. When calling the script from another script, `--` cleanly separates flags from filenames that begin with a dash.

Checklist

Confirm the failing flag is listed in the option string.

Add `:` after a letter that takes a value, and a leading `:` for custom error messages.

Run `shift $((OPTIND - 1))` after the loop.

Use a case branch for `?` that prints usage and exits non-zero.