Parse shell arguments safely with util-linux getopt
You will turn a shell function's positional arguments into a predictable option list, while preserving spaces and shell-special characters. 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 15 minutes for the example and one short script test.
The route
Jump straight to the step you need, or tick off Done means at the end.
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 found first. On this machine, the executable and the manual page are 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 is unexpected:
$ env | grep '^GETOPT_COMPATIBLE=' || true
2. Define the option grammar
The short option string describes one-character options. A letter by itself takes no argument, one colon means a required argument, and two colons mean an optional argument. Long options use 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' --
The -- before the input arguments is the boundary between getopt's own options and the command line being parsed. Keep it in scripts. In the output, getopt emits a second -- before ordinary non-option arguments.
Long options may be abbreviated only when the abbreviation is unambiguous. For example, if both --colour and --collapse exist, --col is an error rather than a guess. Prefer full names in scripts so adding a future option cannot change an old abbreviation's meaning.
3. Preserve the output as shell words
Enhanced getopt quotes its output by default when you use the second or third calling form. The quotes are data for the next shell parse, not decoration. Do not strip them with echo, split the result on whitespace, or use eval set -- $TEMP without quotes.
Here is the small Bash pattern. The temporary variable is needed because eval replaces 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 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
The exact script name is yours. The useful verification is that first file is printed as one argument and that the optional c value produces an empty second parameter.
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 environment variable POSIXLY_CORRECT has the same effect, so a script's result can change if the caller exports 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: 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; otherwise 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
Do not use -u merely 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 that a different shell will interpret Bash-style quoting correctly.
Done means
getopt -Treturned status 4 with no output, confirming the enhanced implementation.- Your short and long option grammar states which arguments are required or optional.
- The script passes
"$@"and evaluates only getopt output after checking its status. - Arguments containing spaces remain one positional parameter.
- You have chosen a scanning mode and considered
POSIXLY_CORRECT. - Invalid, ambiguous and missing-argument cases stop before the parser result is evaluated.