The whole point of getopt
(the implementation from util-linux
or busybox
at least when used the non-traditional way) is that it outputs shell code intended to be evaluated. getopt
does quote the characters that are special in the shell syntax in option arguments¹ making it safe to use there in Bourne-like shells other than yash
².
With the POSIX sh
language, it's the easiest way that a command may return an arbitrary list of strings for the shell.
getopt
implementations that don't support that modus operandi (like the original getopt
which relies on using the split+glob operator instead) are broken by design as they can't return arbitrary lists.
If you don't want to use eval
for some reason, but switching to zsh
is an option, you can use instead:
argv=( "${(@XQ)${(z)$(getopt...)}}" ) || exit
Where the z
parameter expansion flag does the tokenising based on shell quoting syntax (zsh
quoting syntax, but getopt
only uses single quotes for quoting) Q
removes the quotes and X
complains in case of syntax error.
The only advantage is that if getopt
has been replaced by some evil command that outputs evil code, it would not be run, but then again, while evil guy is at replacing getopt
, they might as well get it to run that evil code itself rather than outputting it for you to eval it.
It's possible for a command to communicate an arbitrary list of strings to bash
4.4+ (as opposed to sh
) if you send it NUL-delimited, by reading its output into an array with:
readarray -td '' array < <(cmd) || exit # readarray failed.
wait "$!" || exit # cmd failed
set -- "${array[@]}"
Or same with zsh
:
argv=( "${(0@)$(cmd)}" ) || exit
argv[-1]=() # remove the empty trailing argument caused by the terminating NUL
So one could in theory implement a getopt
that uses that modus operandi.
Note that getopt
is not the only utility that expects you do evaluate its output, see also:
eval "$(dbus-launch --sh-syntax)"
eval "$(dpkg-architecture -u)"
eval "set $(git rev-parse --sq --prefix "$prefix" -- "$@")"
eval "$(lesspipe)"
eval "$(xz --robot --version)"
eval "$(resize)"
eval "$(dircolors)"
Where it's used to modify the environment of the shell.
See also all those that can be used to save the current state and restore later:
aliases=$(alias -L) # zsh
aliases=$(alias -p) # ksh/bash
options=$(set +o)
locale=$(locale)
To restore as eval "$aliases"
, eval "$options"
...
Alternatives to getopt
for parsing options that don't involve using eval
:
- The standard
getopts
builtin, though except in ksh93
³, that only supports single letter options.
zsh
's zparseopts
³
- My
getopts_long
POSIX sh
function
¹ not in option names though at least with version 2.38 which could be seen as a bug, though it should be fine as long as you don't pick silly names for your script's options
² current versions of yash
(as of June 2022) can only deal with valid text so would choke anyway on arguments that cannot be decoded as text in the current locale
³ neither ksh93's getopts
nor zsh's zparseopts
parse the option the same way as GNU getopt_long()
(or util-linux
getopt
). In particular, they don't support abbreviating long options.