Explain use of eval set -- "$ARGS" with getopt in Bash

380 views Asked by At

I'm writing a simple Bash script that requires argument parsing and have composed the following script that uses getopt to parse short or long arguments provided to the script.

#!/bin/bash

# Default values
EXPERIMENT_NAME=""
SW_IMAGE=""
INSTANCE=""
TOTAL_RUNTIME=""
DATASET=""
PRIORITY=""

# Parse command-line options
ARGS=$(getopt -o n:i:s:r:d:p: --long experiment-name:,sw-image:,instance:,total-runtime:,dataset:,priority: -- "$@")
eval set -- "$ARGS"

# Process options and arguments
while true; do
  case "$1" in
    -n | --experiment-name)
      EXPERIMENT_NAME="$2"
      shift 2 ;;
    -i | --sw-image)
      SW_IMAGE="$2"
      shift 2 ;;
    -s | --instance)
      INSTANCE="$2"
      shift 2 ;;
    -r | --total-runtime)
      TOTAL_RUNTIME="$2"
      shift 2 ;;
    -d | --dataset)
      DATASET="$2"
      shift 2 ;;
    -p | --priority)
      PRIORITY="$2"
      shift 2 ;;
    --)
      shift
      break ;;
    *)
      echo "Invalid option: $1"
      exit 1 ;;
  esac
done

# Display captured values
echo "EXPERIMENT_NAME: $EXPERIMENT_NAME"
echo "SW_IMAGE: $SW_IMAGE"
echo "INSTANCE: $INSTANCE"
echo "TOTAL_RUNTIME: $TOTAL_RUNTIME"
echo "DATASET: $DATASET"
echo "PRIORITY: $PRIORITY"

I lifted the following line verbatim from another source but do not understand this.

eval set -- "$ARGS"

From what I've read, this relates to use of positional arguments, but I don't understand if this line would enable use of positional arguments to my script in addition to the short/long options or serves some other function. I also don't understand how to parse the syntax in this line.

I would appreciate a breakdown of the syntax and an overall explanation of this line's utility.

3

There are 3 answers

1
Anil On

From set --help:

      --  Assign any remaining arguments to the positional parameters.
          If there are no remaining arguments, the positional parameters
          are unset.

So the script will construct a string consisting of the following, which we evaluate:

set -- -n 'my_exp' -i 'sushi:1' -s '8gpu' --total-runtime '5d' -- 'any' 'other' 'positional' 'arguments'

We will iterate (with the case statement and shift 2) through the short / long options and the values passed to them, which are now treated as positional arguments, and will break out of this iteration loop we use for parsing when we reach --, allowing for any other positional arguments (in my example literally the arguments: any other positional arguments) to be handled downstream of the getopt parsing.

0
Mirco Ramo On

The eval set -- commands are used to build a positional mapping of your parameters, by assigning any remaining arguments to the positional parameters. so that if you issue set -- a b c, you will have $1=a, $2=b and $3=c.

With getopt, the use of eval is mandatory in order to correctly parse arguments, see https://unix.stackexchange.com/questions/383862/bash-why-is-eval-and-shift-used-in-a-script-that-parses-command-line-arguments#:~:text=You%20need%20a%20way%20to%20safely%20convert%20the%20output%20of%20getopt%20to%20arguments.%20That%20means%20safely%20handling%20special%20characters%20like%20spaces%2C%20%27%2C%20%22%20(quotes)%2C%20*%2C%20etc.%20To%20do%20that%2C%20getopt%20escapes%20them%20in%20the%20output%20for%20interpretation%20by%20the%20shell.

However, it would be safer to avoid the use of eval and use getopts instead, as shown in Bash - how to avoid command "eval set --" evaluating variables.

Off topic: In your example you probably swapped -i and -s, as I suppose -s was referred to the $SW_IMAGE and -i to the $INSTANCE

0
glenn jackman On

GNU getopt returns the parsed arguments as a string. For example, getopt might give you:

ARGS='--experiment-name "a long name with whitespace"'

To process them easily, you replace the current positional parameters with the contents of this string. This is where eval is needed, to enable a second round of shell expansions.

  • without eval, set -- "$ARGS" would give you '--experiment-name "a long name with whitespace"' as a single argument in $1.
  • with eval, the shell can perform word splitting and quote removal to provide '--experiment-name' as $1 and 'a long name with whitespace' as $2

From the getopt(1) man page:

this implementation can generate quoted output which must once again be interpreted by the shell (usually by using the eval command).

And see the example script.