0

While studying the parsing of arguments by bash scripts using getopt, I have almost everywhere watched the use of this construction: eval set -- "$SOMETHING_PARSED". For example, here and here. Since I am going to post my working scripts in the future for free access, I would like not to leave such a hole for users. Are there any ways to avoid this?

It is described here that depending on the version of getopt, you can do without eval. But at least it is not my case. getopt gives a string like this: ' -j --version= '\''1'\'' --number= '\''2'\'' -- '\''2C_IA/GBCs_out/nohope_2C_IA_off. xml'\'''. & set assigns it completely to $1.

Vovin
  • 103
  • I suggest you read that answer again, especially this: "which happily breaks arguments that contain whitespace or look like globs" - that's an actual hole you get from not using eval, as opposed to whatever imaginary hole you think you're leaving for users by using eval. – muru Jun 23 '22 at 15:57

1 Answers1

2

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.