Home / Alt manpages / getopt(1)

  • getopt(1)
  • User command
  • linux

Parse Shell Arguments Safely with util-linux getopt

getopt exists because hand-rolled argument parsing breaks the moment someone passes a value containing a space. The enhanced util-linux version fixes that by quoting its output properly. This guide uses the enhanced getopt(1) from util-linux 2.41.3, with a local manual page labelled util-linux 2.39.3. Allow about fifteen minutes for the example and one short script test.

Checkpoint

This is an argument parser, not a command runner. It does not need root, change files, or alter services. The one security-sensitive step is the usual shell eval needed to turn getopt's quoted text back into positional parameters: use it only with arguments that arrived as shell parameters, never with an untrusted string assembled by concatenation.

1. Confirm the Enhanced Implementation

Check the command your shell finds first. On this machine the executable and the manual page come from different installed util-linux locations, so record both when debugging a deployment:

$ command -v getopt
/home/linuxbrew/.linuxbrew/bin/getopt
$ getopt --version
getopt from util-linux 2.41.3
$ getopt -T
$ printf 'test status: %s\n' "$?"
test status: 4

-T deliberately returns status 4 and prints nothing for the enhanced implementation: that is success for this test. An old implementation, or this one with GETOPT_COMPATIBLE set, returns status 0 and prints -- instead. Check the environment if the result surprises you:

$ env | grep '^GETOPT_COMPATIBLE=' || true

2. Define the Option Grammar

The short option string describes one-character options: a letter alone takes no argument, one colon means a required argument, two colons mean an optional argument. Long options follow the same colon convention and are comma-separated.

This grammar accepts -a or --a-long, requires a value for -b or --b-long, and gives -c or --c-long an optional value:

$ getopt -o 'ab:c::' \
    --long 'a-long,b-long:,c-long::' \
    -n example.bash -- \
    -a 'first file' --c-long -b 'very long'
 -a 'first file' --c-long '' -b 'very long' --
  • Keep the first --. It marks the boundary between getopt's own options and the command line being parsed.
  • Expect a second --. getopt emits one of its own before ordinary non-option arguments in the output.
  • Prefer full long names in scripts. Abbreviations only work while they are unambiguous: if both --colour and --collapse exist, --col is an error, not a guess, and adding a future option could change what an old abbreviation means.

3. Preserve the Output as Shell Words

Enhanced getopt quotes its output by default when you use the second or third calling form. Those quotes are data for the next shell parse, not decoration. Do not strip them with echo, split the result on whitespace, or run eval set -- $TEMP unquoted.

Here is the small Bash pattern. The temporary variable exists because eval would otherwise overwrite the status that getopt returned:

TEMP=$(getopt -o 'ab:c::' \
    --long 'a-long,b-long:,c-long::' \
    -n example.bash -- "$@")
if [ "$?" -ne 0 ]; then
    printf '%s\n' 'Invalid options' >&2
    exit 1
fi
eval set -- "$TEMP"
unset TEMP

while true; do
    case "$1" in
        -a|--a-long) printf '%s\n' 'Option a'; shift ;;
        -b|--b-long) printf 'Option b: %s\n' "$2"; shift 2 ;;
        -c|--c-long)
            if [ -n "$2" ]; then
                printf 'Option c: %s\n' "$2"
            else
                printf '%s\n' 'Option c: no argument'
            fi
            shift 2 ;;
        --) shift; break ;;
        *) printf 'Internal parser error: %s\n' "$1" >&2; exit 1 ;;
    esac
done

for arg do
    printf 'Argument: %s\n' "$arg"
done

Run the function or script with the arguments kept as separate shell words:

$ ./example.bash -a 'first file' --c-long -b 'very long' 'remaining item'
Option a
Option c: no argument
Option b: very long
Argument: first file
Argument: remaining item

Checkpoint

first file printed as one argument, and the optional c value produced an empty second parameter. The exact script name is yours; those two facts are what matter.

4. Understand Non-Option Scanning

By default, GNU option parsing can continue past an ordinary argument, and getopt outputs ordinary arguments after the recognised options. A literal -- always ends option parsing.

Put + first in the short option string when the first ordinary argument should stop scanning. The POSIXLY_CORRECT environment variable has the same effect, so a script's result can change if the caller happens to export it:

$ getopt -o '+ab:' -- -a item -b value
 -a -- 'item' '-b' 'value'
$ getopt -o 'ab:' -- -a item -b value
 -a -b 'value' -- 'item'

A leading - in the short option string is a different mode again: non-option arguments are emitted where they occur, although getopt still generates a final --. Choose one mode deliberately and document it beside the grammar.

5. Handle Failures Before Evaluating Anything

A missing required argument, unknown option, or ambiguous long option produces a non-zero status. Stop before eval, or you risk parsing an incomplete result:

$ getopt -o 'ab:' -- -b
getopt: option requires an argument -- 'b'
 --
$ printf 'status: %s\n' "$?"
status: 1
$ getopt -o '' --long 'colour,collapse' -- --col
getopt: option '--col' is ambiguous; possibilities: '--colour' '--collapse'
 --
$ printf 'status: %s\n' "$?"
status: 1

Warning

Do not use -u just to make the output look simpler. Unquoted output can split an argument containing whitespace or special characters. Likewise, do not use GETOPT_COMPATIBLE in an enhanced Bash parser unless you have deliberately accepted the older output rules.

6. Keep Changes Reversible

The examples only parse arguments and start no child command, so there is nothing to undo and no elevated privilege requirement. If you add an eval parser to an existing script, save the script first and run its tests with harmless commands. Do not replace a service wrapper in place during a live maintenance window; keep the previous wrapper available and restore it if the new parser changes the command's arguments.

For a shell other than Bash, select the matching quoting convention with -s sh, -s csh, or -s tcsh. The installed manual documents those four values. Do not assume a different shell will interpret Bash-style quoting correctly.

Done means

  • Implementation confirmed. getopt -T returned status 4 with no output.
  • Grammar defined. Your short and long option grammar states which arguments are required or optional.
  • Status checked first. The script passes "$@" and evaluates getopt's output only after checking its status.
  • Spaces preserved. Arguments containing spaces remain one positional parameter.
  • Scanning mode chosen. You picked a scanning mode deliberately and considered POSIXLY_CORRECT.
  • Failures caught early. Invalid, ambiguous and missing-argument cases stop before the parser result is evaluated.