Search

Getopt and Getopts

0 views

Handling command-line options with getopt

When you write a shell script that will be called from the command line, you usually want to accept a mixture of short flags, long flags, and positional arguments. The getopt utility exists to separate those pieces, verify that each option is valid, and arrange them into a predictable sequence that your script can read easily. Even though many people use getopt sparingly, mastering its quirks saves you from headaches when the script is run on different systems.

The basic idea behind getopt is to rewrite the original argument list into a canonical form: every short option becomes a separate argument, options that need a value are followed by their value, and the special delimiter marks the end of options. A typical invocation looks like this:

Prompt
set -- $(getopt -o a:b:c: -l alpha,beta,gamma: -- "$@")</p>

Here, -o a:b:c: tells getopt that the short flags -a, -b, and -c all expect an argument. The -l alpha,beta,gamma: part lists the long options that have the same relationship. The signals the end of the option list for getopt itself; anything that follows is passed unchanged. Finally, -- "$@" passes the script's original arguments to the utility.

Once the rewrite is complete, the shell receives a clean set of arguments. If the user runs the script as script.sh -a 1 -b 2 --gamma 3 foo bar, the rewritten list looks like:

Prompt
-a 1 -b 2 --gamma 3 foo bar</p>

Notice that foo bar remains as two separate arguments. This is intentional: getopt cannot preserve spaces inside a single argument unless the user quotes it. If you write script.sh -a "one two", older versions of getopt will split that into two tokens, while newer GNU versions honour the quote and keep it as one argument. To be safe across platforms, always quote the entire getopt call:

Prompt
set -- "$(getopt -o a:b:c: -l alpha,beta,gamma: -- "$@")"</p>

This subtle difference becomes apparent when you try a case that mixes bunched flags and spaced flags. For example, script.sh -ab foo versus script.sh -a foo -b should both be treated the same. The rewritten list will show the flags in the order you typed them, but getopt guarantees that each flag that expects an argument receives the correct value.

One area that often trips up script authors is error handling. By default, if the user supplies an option that you did not list, getopt prints a diagnostic to stderr and aborts the rewrite. This behavior is helpful when you want strict validation. However, if you prefix the option string with a colon - getopt -o :a:b:c: - getopt suppresses its own error messages and instead returns the offending flag in a way that you can inspect later. For instance, running script.sh -x after enabling silent mode would give you a rewritten list that includes the unrecognized -x flag, allowing your script to decide how to handle it.

In many modern Linux distributions, the default getopt implementation is the GNU version, which supports long options and better quoting. On older systems like SCO OSR5, early Mac OS X releases, or older FreeBSD, you might encounter a more primitive version that ignores quotes entirely. If you need your script to run on those systems, you can embed a conditional that checks the version string of getopt and adjusts the syntax accordingly. The simplest fallback is to avoid long options and keep everything in short form.

Testing getopt scripts is straightforward if you print out the argument list after the rewrite. A tiny helper line can make debugging a breeze:

Prompt
echo "After getopt: $@"</p>

Running this inside your script before you start processing the options lets you verify that getopt did what you expected. It also exposes subtle quoting problems early on, saving you from cryptic errors later when you try to reference a value that was split inadvertently.

Once you have a clean argument list, the rest of your script can loop through it normally. A common pattern looks like this:

Prompt
while [[ $# -gt 0 ]]; do</p> <p> case "$1" in</p> <p> -a) val_a="$2"; shift 2 ;;</p> <p> -b) val_b="$2"; shift 2 ;;</p> <p> -c) val_c="$2"; shift 2 ;;</p> <p> --) shift; break ;;</p> <p> *) echo "Unknown option: $1"; exit 1 ;;</p> <p> esac</p> <p>done</p>

This loop consumes each flag and its value, then moves on to the next. Because getopt has already arranged the arguments in a predictable order, the loop is simple and reliable. The only complication is ensuring that any positional arguments that follow the delimiter are processed after the loop.

In short, getopt offers a powerful, portable way to validate options, handle both short and long forms, and enforce argument requirements. By quoting the call, checking the version, and printing the rewritten list during testing, you can avoid many of the common pitfalls that new scripts encounter. With a small amount of boilerplate, your script will accept flexible flag orders, bundled or spaced options, and quoted values without breaking on different Unix flavors.

Simplifying option parsing with getopts

The built‑in getopts command is available in most POSIX shells, including sh and bash. Because it lives inside the shell, getopts avoids the overhead of spawning an external process and gives you tighter control over the parsing loop. The syntax is a bit different from getopt, but once you get the hang of it, the result is cleaner code and fewer edge cases to worry about.

A typical getopts invocation looks like this:

Prompt
while getopts ":a:b:c:" opt; do</p> <p> case "$opt" in</p> <p> a) val_a="$OPTARG" ;;</p> <p> b) val_b="$OPTARG" ;;</p> <p> c) val_c="$OPTARG" ;;</p> <p> \?) echo "Invalid option: -$OPTARG" >&2 ;;</p> <p> :) echo "Option -$OPTARG requires an argument" >&2 ;;</p> <p> esac</p> <p>done</p> <p>shift $((OPTIND - 1))</p>

The first argument to getopts is the option string. Each letter represents a short flag, and a trailing colon indicates that the flag expects an argument. The leading colon suppresses the default error messages so that you can handle errors yourself. The second argument is the variable name that will receive the current flag value each time the loop runs. The built‑in uses two special variables: $opt holds the current flag, and $OPTARG holds the associated argument if the flag requires one.

One of the biggest advantages of getopts is that it never modifies the original $@ list. Instead, it reads the arguments one by one, leaving the rest of the script untouched. After the loop completes, the variable OPTIND indicates the index of the next argument to be processed. By shifting the positional parameters to discard the options, you can then handle any remaining values as regular positional arguments:

Prompt
<code>shift $((OPTIND - 1)) <h1>Now $1, $2, ... hold the positional arguments

Because getopts processes arguments in order, you can intermix flags and positional values if you wish. However, the convention is to place all options first, followed by or simply the remaining positional arguments. If you need to support long options (e.g., --alpha), you’ll have to implement a small pre‑parse step or use getopt to translate them into short forms before handing them to getopts

Handling missing arguments is straightforward. If a user omits the value for a flag that expects one, getopts sets $opt to : and $OPTARG to the flag name. By checking for that colon case, you can display a helpful message or terminate the script. For example:

Prompt
if [[ $opt == ":" ]]; then</p> <p> echo "Missing argument for option -$OPTARG" >&2</p> <p> exit 1</p> <p>fi</p>

Unknown flags trigger a \? case. Because getopts does not automatically print an error, you can decide whether to ignore the flag, exit with a message, or add it to a list for later processing. A common pattern is to treat unknown flags as a request for help or a custom action.

Resetting OPTIND lets you re‑parse the argument list in a different context. This is useful when you need to process options in multiple passes - for instance, a preliminary scan to determine a mode, followed by a second scan that depends on that mode. To reset, simply set OPTIND back to 1:

Prompt
OPTIND=1</p> <p>while getopts ":a:b:" opt; do</p> <p> # second pass logic</p> <p>done</p>

Because getopts is a shell builtin, it behaves the same across all POSIX‑compliant shells. This consistency is a major advantage over the external getopt, which can differ in options and quoting behavior between systems. However, the trade‑off is that getopts handles only short options by default; you cannot specify long option names without writing extra code.

When debugging getopts, echoing the values of $opt and $OPTARG inside the loop provides a quick sanity check. For example:

echo "flag=$opt, arg=$OPTARG"

done

This simple output lets you confirm that the parser is recognizing flags and arguments as intended. If you see unexpected values, double‑check the option string and ensure that any required arguments are present on the command line.

In practice, getopts is ideal for scripts that need predictable, fast option parsing without external dependencies. It works reliably on old systems, avoids the pitfalls of quoting with getopt, and gives you full control over error handling. By following the patterns above - explicit option string, error checking, shifting OPTIND, and optional reset - you can write robust scripts that accept a wide range of flag combinations while remaining portable across Unix and Linux environments.

Suggest a Correction

Found an error or have a suggestion? Let us know and we'll review it.

Share this article

Comments (0)

Please sign in to leave a comment.

No comments yet. Be the first to comment!

Related Articles