user7988893
user7988893

Reputation:

Getopt generates a double-dash (--) even if there's none on the command line, and doesn't validate an extraneous argument

I'm learning the getopt command and using the following diagnostic script to study its workings:

$ cat test-getopt.sh
#!/bin/bash
args=`getopt ab:c $*`
set -- $args
for i
do
    echo "-->$i"
done
echo $#

I cannot understand its behavour in the following cases. Could you clarify?

I've already skimmed through the getopt man page as well as some tutorials but couldn't quite work out a clear explanation.

Upvotes: 0

Views: 2650

Answers (2)

ivan_pozdeev
ivan_pozdeev

Reputation: 36008

According to getopt manpage:

Normally, no non-option parameters output is generated until all options and their arguments have been generated. Then '--' is generated as a single parameter, and after it the non-option parameters in the order they were found, each as a separate parameter.

I.e. -- by itself is generated to signify the end of options. (And after it, positional parameters are generated if there are any.)

I guess this is done for uniformity -- to use the same code logic regardless of whether the user specified -- on the command line or not.


In the 2nd case, c is a positional argument. Positional arguments are not checked by getopt in any way and are rather passed as-is. The manpage doesn't say anything about validating non-option arguments:

getopt is used to break up (parse) options in command lines for easy parsing by shell procedures, and to check for legal options.


Finally, note that to correctly process arguments with whitespace, you need to: use $@ instead of $*; quoting; eval with set; and use the enhanced mode of getopt -- as per Example of how to parse options with bash/getopt. Also should use bash -e mode to quit the program on an invalid option:

#!/bin/bash -e
args=`getopt -o ab:c -- "$@"`
eval set -- "$args"
for i
do
  echo "-->$i"
done
echo $#

$ ./test-getopt.sh -b "arg ument"
-->-b
-->arg ument
-->--
3

$ ./test-getopt.sh -d ; echo $?
getopt: unknown option -- d
1

Also, a while loop with shift as per the same example could be more convenient that for as it: makes it easy to get the next argument -- to get the option's argument and check if there is an argument if it's optional; check the number of the remaining (positional) arguments when you're done with options.

Upvotes: 2

ghoti
ghoti

Reputation: 46846

I normally use constructs like this to run getopts:

# Set defaults
opt_a=0; opt_b=""; opt_c=false

# Step through options
while getopts ab:c opt; do
        case "$opt" in
                a) opt_a=1 ;;
                b) opt_b="${OPTARG:?The -b option requires an argument.}" ;;
                c) opt_c=true ;;
                *) usage; exit 64 ;;
        esac
done
shift $((OPTIND - 1))

Use of shift like this at the end causes your positional arguments to be shifted back such that the first argument that getopts can't process becomes $1. For example, if the above snippet was part of a script named foo, one might run:

$ foo -ab meh smoo blarg

which would set $opt_a to 1, $opt_b to "meh", $1 to "smoo" and $2 to "blarg" for the portion of the script following the snippet.

Upvotes: 0

Related Questions