Skip to content

Instantly share code, notes, and snippets.

@magnetikonline
Last active April 30, 2024 15:48
Show Gist options
  • Save magnetikonline/22c1eb412daa350eeceee76c97519da8 to your computer and use it in GitHub Desktop.
Save magnetikonline/22c1eb412daa350eeceee76c97519da8 to your computer and use it in GitHub Desktop.
Bash getopt long options with values usage example.

Bash getopt long options with values usage example

#!/bin/bash -e

ARGUMENT_LIST=(
  "arg-one"
  "arg-two"
  "arg-three"
)


# read arguments
opts=$(getopt \
  --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
  --name "$(basename "$0")" \
  --options "" \
  -- "$@"
)

eval set --$opts

while [[ $# -gt 0 ]]; do
  case "$1" in
    --arg-one)
      argOne=$2
      shift 2
      ;;

    --arg-two)
      argTwo=$2
      shift 2
      ;;

    --arg-three)
      argThree=$2
      shift 2
      ;;

    *)
      break
      ;;
  esac
done

Note

The eval in eval set --$opts is required as arguments returned by getopt are quoted.

Example

$ ./getopt.sh --arg-one "apple" --arg-two "orange" --arg-three "banana"

Reference

@s-belichenko
Copy link

s-belichenko commented Nov 27, 2018

Its not worked with empty arguments. If we run:
$ ./getopt.sh --arg-one "apple" --arg-two --arg-three "banana"
we will be have argTwo=--arg-three

@magnetikonline
Copy link
Author

That's right @s-belichenko - you need to adjust the shift count to shift 1 for those arguments.

Should probably update the example.

@slowpeek
Copy link

Even though this works (to some extent, keep reading):

eval set --$opts

it looks really weird and confusing. It works because getopt output starts with a space. But in readers eyes it looks like you call set with some double-dashed option namely --$opts. For the sake of clarity it must be written as

eval set -- $opts

Another story is $opts must be quoted above. I see it in the commits history first you used set --$opts but later you prefixed it with eval. It surely is right, because otherwise any option value containing a space would split into parts. It could be the end of the story, but this is exactly the time when more spaces matter:

> eval set -- $(getopt -o a: -- -a '>    <'); printf 'arg=%s\n' "$@"
arg=-a
arg=> <
arg=--
> eval set -- "$(getopt -o a: -- -a '>    <')"; printf 'arg=%s\n' "$@"
arg=-a
arg=>    <
arg=--
> 

As you see, if unquoted, the value undergoes word-splitting and sequences of chars from $IFS are replaces with a single space in the printed args.

There is one more problem with it being called a 'usage example': it makes a false impression getopt doesnt fail. It should be at least this:

opts=$(getopt \
    --longoptions "$(printf "%s:," "${ARGUMENT_LIST[@]}")" \
    --name "$(basename "$0")" \
    --options "" \
    -- "$@"
) || exit

You might say -e bash option hidden in the shebang would silently kill the script on errors, but it is not a good idea for two reasons:

  1. Compatibility-wise the shebang should be /usr/bin/env bash. Because of SC2096 you cant make it both compatible and with -e option to bash.
  2. It is hidden out of clear sight and is not explicitly spoken out, bad for learners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment