skip to content

A bash script begins its option loop with `while getopts "hf:v" opt; do`. Which of h, f and v takes a value, where does bash put that value, and how are `-f out.txt`, `-fout.txt` and `-vf out.txt` each parsed?

level: juniorimportance: should knowfreq 52%

answer

  1. read the optstring letter by letter
  2. the punctuation after a letter matters
  3. colon means this option takes a value
  4. letter in opt, value somewhere else
  5. OPTARG, not $2

basics

~20 s

A trailing colon means that option takes a value: -f does, h and v do not. getopts puts the option letter in opt and -f's value in OPTARG, accepting -f out.txt, -fout.txt and -vf out.txt alike.

solid answer

~40 s

In the getopts optstring `hf:v`, a colon *after* a letter means that option requires a value, so only `-f` takes one; `-h` and `-v` are plain flags. On each iteration getopts assigns the option letter to the variable you named — here `opt` — and, for an option that takes a value, assigns the value to the shell variable `OPTARG`. You read it as `$OPTARG` inside that branch, not as `$2`. All three spellings are equivalent: `-f out.txt` (value as the next word), `-fout.txt` (value attached), and `-vf out.txt` (flags bundled, with the last one taking the following word). Bundling means `-vf out.txt` runs the loop body twice: once with `opt=v`, once with `opt=f` and `OPTARG=out.txt`.

go deeper

for a junior

Be able to read an optstring out loud: letters are the accepted flags, and a colon after a letter means that flag takes a value which arrives in OPTARG.

for a middle

Explain the mechanics — getopts consumes one character per iteration, which is why bundled forms like -vf work and why only the last letter of a bundle may take a value.

for a senior

Show that you design the option surface, not just parse it: keep value-taking options few and obvious, quote OPTARG everywhere, and validate the value inside its branch rather than far downstream.

for a principal

Own the convention across a team's scripts: which flags every tool shares, whether short-only parsing is acceptable for your users, and when a tool's interface has grown past what an optstring can express.

## What an optstring actually encodes `getopts` is a shell builtin (POSIX, and present in bash) whose job is to walk the script's arguments one option at a time. Its first argument is the *optstring*: a compact list of the single letters your script accepts. A letter on its own is a boolean flag. A letter followed by a colon takes a value. So `hf:v` declares three options — `-h`, `-f VALUE`, `-v` — and nothing else. The colon binds to the letter *before* it. `hf:v` means `-f` takes a value; it says nothing about `-v`. Reading the colon as belonging to the following letter is the single most common beginner error here. ## The two variables getopts writes Each successful call to getopts writes two things: - the **option letter** into the variable you name as its second argument (`opt` by convention, but any name works); - the **option's value**, if that option was declared with a colon, into `OPTARG`. `OPTARG` is a plain shell variable owned by getopts; it is overwritten on every iteration, so copy it into your own variable inside the matching branch rather than reading it after the loop. ```bash outfile="" verbose=0 while getopts "hf:v" opt; do case $opt in h) usage; exit 0 ;; f) outfile=$OPTARG ;; v) verbose=1 ;; esac done ``` The loop condition is the whole mechanism: getopts returns success while there is another option to report, and returns non-zero when it runs out, which ends the `while`. ## Why $OPTARG and not $2 A hand-rolled parser reaches for `$2` to grab the value after `-f`. That is wrong under getopts for two reasons. First, getopts does not shift the positional parameters as it goes, so after seeing `-f` the value may or may not still be `$2`. Second, the value is not always a separate word at all — `-fout.txt` is legal, and there `$2` is something else entirely. `OPTARG` normalises all of that: whichever spelling the caller used, the value arrives in `OPTARG`. ## The three spellings Given `getopts "vf:" opt`, all of these pass `out.txt` as the value of `-f`: ```bash script -v -f out.txt # separate flag, value as next word script -v -fout.txt # value attached to the option letter script -vf out.txt # bundled flags; the last one takes the next word ``` Bundling (also called clustering) works because getopts consumes one *character* at a time from the current word. In `-vf`, it reports `v` first, then comes back to the same word for `f`, sees that `f` needs a value, and takes the following word. Only the last letter in a bundle may take a value, because everything after a value-taking letter in the same word is read as the value: in `-fv`, `OPTARG` becomes the literal string `v`. ## What this loop does not do for you Two limits are worth knowing from day one. getopts handles **single-character options only** — there is no way to declare `--verbose` in an optstring. And getopts does not remove the options it parsed from the positional parameters; a separate `shift` after the loop is what leaves your script's real arguments in `$1`, `$2`, and so on. ## Common mistakes ```bash # WRONG: value read positionally f) outfile=$2 ;; # WRONG: colon read as belonging to the next letter while getopts "h:fv" opt # this declares -h as taking a value # WRONG: OPTARG read after the loop done echo "$OPTARG" # holds whatever the last value-taking option set, if any ``` Quoting still applies normally: write `outfile=$OPTARG` or `outfile="$OPTARG"` and always quote it when you use it, because a caller can pass a value containing spaces.

  • With the optstring "vf:", what does OPTARG contain if the script is run as `script -fv`?
    The literal string `v`. Once getopts reaches a letter that requires a value, everything remaining in that same word becomes the value — so `-fv` means `-f` with the value `v`, not the two flags `-f` and `-v`. Only the final letter of a bundle may be one that takes a value.
  • Can an option declared with a colon be made optional, so that -f works both with and without a value?
    Not with bash's getopts. A colon-suffixed option always requires a value; if none follows, getopts reports an error rather than a bare flag. If you need both behaviours, declare two separate options, or give the flag a default and let a second option override it. Optional-argument options are a GNU `getopt_long` feature, not a getopts one.

saying these in an interview costs you the question

  • Thinks the colon applies to the letter that follows it
  • Reads the option's value from $2 instead of OPTARG
  • Claims -fout.txt without a space is a syntax error
  • Says OPTARG holds the option letter rather than its value
  • Believes getopts needs one call per option

context