4

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 ?

Rachid K.
  • 4,490
  • 3
  • 11
  • 30

1 Answers1

3

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").

Rachid K.
  • 4,490
  • 3
  • 11
  • 30
  • You didn't try `t.sh -- -v`? – Fravadona Sep 11 '22 at 19:38
  • 1
    I tried it : it works as it should. The double "-" marks the end of the options. Everything behind is considered as additional parameters. – Rachid K. Sep 11 '22 at 21:22
  • Unix utilities use `--` to indicate *End Of Options*. For example, try `printf "--\n"` and then `printf -- "--\n"` – David C. Rankin Sep 12 '22 at 07:35
  • @DavidC.Rankin: This is what I said. For example, this is typically used to pass the additional parameters to some program executed by the tool. The question here, is the side effect of "-" in the optstring of getopts. Hence, I said "--" continues to play its USUAL role when used with at least a space behind it. – Rachid K. Sep 12 '22 at 07:37
  • @RachidK. - from the man page under SCANNING MODE it would seem if the first option **character** is `'-'` the special scanning mode where non-option parameters are output in place instead of being collected at the end after parsing. That's all I find about the `'-'` option character. – David C. Rankin Sep 12 '22 at 07:48
  • @DavidC.Rankin: yes we agree. The question was to know if putting "-" at the end followed by a ":" is on purpose or a simple bug. Hence my empirical approach to see if this is really on purpose. Hence I have the impression that I found some useful behavior but as it is not documented, I am likely promoting the side effect of a possible bug... – Rachid K. Sep 12 '22 at 07:51
  • 2
    It's a good question -- I haven't tried it in 22 years of using Linux, but from the way getopt works generally in processing options and there arguments and then collecting and moving any non-option arguments to the end of the argument list (so you can process them as program arguments) -- it makes sense there would be an option to say "Don't collect any non-option arguments". The special scanning mode associated with `'-'` as the option character in short-option mode -- seems to be that. – David C. Rankin Sep 12 '22 at 07:54
  • I really feel like using it in my scripts – Fravadona Sep 12 '22 at 18:50
  • I added the verification in the source code of bash. This is the result of the path where the parameter is concatenated to the option (e.g. -xfoo = option x and parameter foo). – Rachid K. Sep 14 '22 at 12:22