StudentOrint
StudentOrint

Reputation: 59

What is the meaning of "-" in getopts template?

What is the meaning of the following template in getopts?

while getopts ':x:y-:' val;

I know that it expects two options -x or -y but what is the meaning of the symbol - at the end of the template ?

Upvotes: 4

Views: 131

Answers (1)

Rachid K.
Rachid K.

Reputation: 5211

Empirical approach

I was unable to find any documentation about this "-" in the option string. So, I tried an empirical approach to see how it influences the behavior of getopts. I found that passing "--something" to the script (without spaces after "--") makes it accept "--" as an option and report "something" in OPTARG:

#!/bin/bash

xopt=
yopt=
mopt=

while getopts ':x:y-:' val
do
  case $val in
    x) xopt=1
       xval="$OPTARG";;
    y) yopt=1;;
    -) mopt=1
       mval="$OPTARG";;
    ?) echo "Usage: $0: [-x value] [-y] [--long_opt_name] args" >&2
       exit 2;;
  esac
done

[ ! -z "$xopt" ] && echo "Option -x specified with parameter '$xval'"

[ ! -z "$yopt" ] && echo "Option -y specified"

[ ! -z "$mopt" ] && echo "Option -- specified with optname '$mval'"

shift $(($OPTIND - 1))
echo "Remaining arguments are: $*"

Examples of executions:

$ t.sh --v                    
Option -- specified with optname 'v'
Remaining arguments are:
$ t.sh --vv other1 other2
Option -- specified with optname 'vv'
Remaining arguments are: other1 other2
$ t.sh --help -x 123 -y others
Option -x specified with parameter '123'
Option -y specified
Option -- specified with optname 'help'
Remaining arguments are: others
$ t.sh --help -x 123 -- -y others
Option -x specified with parameter '123'
Option -- specified with optname 'help'
Remaining arguments are: -y others
$ t.sh  -y -x val --x -- param1 -h -j -x -y
Option -x specified with parameter 'val'
Option -y specified
Option -- specified with optname 'x'
Remaining arguments are: param1 -h -j -x -y

Would it be a "hidden" feature to manage gnu-like long options but without parameters (i.e. only the "--long_opt_name") or am I promoting the side effect of a bug? Anyway, using such undocumented behavior is not advised as this may change after some future fixes or evolution of the command.

Nevertheless, if spaces are put after the double "-", the latter continues to play its usual documented role separating options from additional parameters:

$ t.sh --help -y -x val -- param1 -h -j -x -y
Option -x specified with parameter 'val'
Option -y specified
Option -- specified with optname 'help'
Remaining arguments are: param1 -h -j -x -y
$ t.sh -- -v                           
Remaining arguments are: -v

Verification in the source code

As getopts is a builtin of bash, I downloaded its source code (version 5.0) from here. The builtins are located in the eponym sub-directory. getopts source code is: builtins/getopts.def. For each argument on the command line, it calls sh_getopt(argc, argv, optstr). This function is defined in builtins/getopt.c:

[...]
int
sh_getopt (argc, argv, optstring)
     int argc;
     char *const *argv;
     const char *optstring;
{
[...]
  /* Look at and handle the next option-character.  */

  c = *nextchar++; sh_charindex++;
  temp = strchr (optstring, c);

  sh_optopt = c;

  /* Increment `sh_optind' when we start to process its last character.  */
  if (nextchar == 0 || *nextchar == '\0')
    {
      sh_optind++;
      nextchar = (char *)NULL;
    }

  if (sh_badopt = (temp == NULL || c == ':'))
    {
      if (sh_opterr)
    BADOPT (c);

      return '?';
    }

  if (temp[1] == ':')
    {
      if (nextchar && *nextchar)
    {
      /* This is an option that requires an argument.  */
      sh_optarg = nextchar;
      /* If we end this ARGV-element by taking the rest as an arg,
         we must advance to the next element now.  */
      sh_optind++;
    }
      else if (sh_optind == argc)
    {
      if (sh_opterr)
        NEEDARG (c);

      sh_optopt = c;
      sh_optarg = "";   /* Needed by getopts. */
      c = (optstring[0] == ':') ? ':' : '?';
    }
      else
    /* We already incremented `sh_optind' once;
       increment it again when taking next ARGV-elt as argument.  */
    sh_optarg = argv[sh_optind++];
      nextchar = (char *)NULL;
    }
  return c;
}

In the previous source lines, nextchar points on the option character (i.e. the one located right after '-') in argv[] and temp points on the option character in the optstring (which is '-'). We can see when temp[1] == ':' (i.e. the optstring specifies "-:"), sh_optarg is set with the incremented value of nextchar which is the first letter of the option name located behind "--".
In our example, where optstring is ":x:y-:" and we pass to the script "--name", the above code does:

optstring = ":x:y-:"
                 ^
                 |
                temp

argv[x] = "--name"
            ^^
           /  \
          c  nextchar (+ 1)

temp[1] == ':' ==> sh_optarg=nextchar="name"

Hence, with the above algorithm in bash, when ":-" is specified in the option string, any "--name" option on the command line reports "name" in OPTARG variable.

This is merely the output of the code path when the parameter is concatenated to the option name (e.g. -xfoo = option "x" and parameter "foo", --foo = option "-" and parameter "foo").

Upvotes: 3

Related Questions