Getopt Fails to Detect Missing Argument for Option

getopt fails to detect missing argument for option

See the POSIX standard definition for getopt. It says that

If it [getopt] detects a missing
option-argument, it shall return the
colon character ( ':' ) if the first
character of optstring was a colon, or
a question-mark character ( '?' )
otherwise.

As for that detection,

  1. If the option was the last character in the string pointed to by
    an element of argv, then optarg shall
    contain the next element of argv, and
    optind shall be incremented by 2. If
    the resulting value of optind is
    greater than argc, this indicates a
    missing option-argument, and getopt()
    shall return an error indication.
  2. Otherwise, optarg shall point to the string following the option
    character in that element of argv, and
    optind shall be incremented by 1.

It looks like getopt is defined not to do what you want, so you have to implement the check yourself. Fortunately, you can do that by inspecting *optarg and changing optind yourself.

int c, prev_ind;
while(prev_ind = optind, (c = getopt(argc, argv, ":a:b:c")) != EOF)
{
if ( optind == prev_ind + 2 && *optarg == '-' ) {
c = ':';
-- optind;
}
switch ( …

getopt: unable to recognize the missing argument ' : ' and invalid ones ' ? '

The POSIX version of the getopt() function specifies:

If getopt() encounters an option character that is not contained in optstring, it shall return the <question-mark> ( '?' ) character. If it detects a missing option-argument, it shall return the <colon> character ( ':' ) if the first character of optstring was a <colon>, or a <question-mark> character ( '?' ) otherwise.

Since your optstr does not start with a :, it should return a ? instead.

How to prevent getopt from being confused with option with missing argument?

It is perfectly OK to have an option argument that starts with a dash and generally resembles another option. There is no reason for getopt to report an error.

If a program doesn't want to accept such option arguments, it should specifically check for them, e.g.

   if (optarg[0] == '-') {
// oops, looks like user forgot an argument
err("Option requires an argument");
}

Getopt - adding case for both missing option and missing non-option argument

Well, you can do exactly what you want to do with getopt, and more, you just need to adjust your logic slightly. Specifically, you can setup getopt so that:

fizzle.exe                        /* reads from stdin   */
fizzle.exe -a /* reads from stdin */
fizzle.exe -a textfile.txt /* reads textfile.txt */
fizzle.exe textfile.txt /* reads textfile.txt */

Here is a short example of implementing the above logic around the -a option. The last case also specifies that, if give, the first unhandled option after all options are given will also be taken as the filename to read.

#include <stdio.h>
#include <unistd.h> /* for getopt */

int main (int argc, char **argv) {

int opt;
char *fn = NULL;
FILE *pointerFile = stdin; /* set 'stdin' by default */

while ((opt = getopt (argc, argv, "a::b:")) != -1) {
switch (opt) {
case 'a' : /* open file if given following -a on command line */
if (!optarg) break; /* if nothing after -a, keep stdin */
fn = argv[optind - 1];
pointerFile = fopen (fn, "r");
break;
case 'b' :; /* do whatever */
break;
default : /* ? */
fprintf (stderr, "\nerror: invalid or missing option.\n");
}
}
/* handle any arguments that remain from optind -> argc
* for example if 'fizzle.exe textfile.txt' given, read from
* textfile.txt instead of stdin.
*/
if (pointerFile == stdin && argc > optind) {
fn = argv[optind++];
pointerFile = fopen (fn, "r");
}

printf ("\n fizzle.txt reads : %s\n\n", pointerFile == stdin ? "stdin" : fn);

return 0;
}

Example Use/output

$ ./bin/optfizzle

fizzle.exe reads : stdin

$ ./bin/optfizzle -a

fizzle.exe reads : stdin

$ ./bin/optfizzle -a somefile.txt

fizzle.exe reads : somefile.txt

$ ./bin/optfizzle someotherfile.txt

fizzle.exe reads : someotherfile.txt

Look it over and let me know if you have any questions.

Getopts behaves not as expected when option parameter is missing

I would recommend to test whether there are three unprocessed arguments left once getopts has finished its work. If it is not the case, abort and print an error message.

For example, add this at the end of the script:

shift "$((OPTIND-1))" 
if [ ! $# -eq 3 ] ; then
echo "Expected three mandatory arguments"
exit 1
fi

Forcing an error if the argument of -p is omitted, is not directly supported. As a workaround, you can test whether the next argument is either missing or starts with a dash. For instance:

    p)  if [ -z "$OPTARG" -o "${OPTARG:0:1}" = "-" ] ; then
echo "Error: -p requires an argument"
exit 1
fi
echo "-p triggered, param: $OPTARG"
altpath=$OPTARG
;;

Here is the full script:

#!/bin/bash

verbose=
altpath=
while getopts ":vp:" opt; do
case $opt in
v) echo "-v triggered"
verbose=true
;;
p) if [ -z "$OPTARG" -o "${OPTARG:0:1}" = "-" ] ; then
echo "Error: -p requires an argument"
exit 1
fi
echo "-p triggered, param: $OPTARG"
altpath=$OPTARG
;;
\?) echo "invalid option: -$OPTARG."
exit 1
;;
:) echo "option -$OPTARG requires an argument."
exit 1
;;
esac
done

shift "$((OPTIND-1))"
if [ ! $# -eq 3 ] ; then
echo "Expected three mandatory arguments"
exit 1
fi

How could one determine that required argument of option is missing?

The only way that I can see to accomplish your goal of distinguishing between an invalid option and a valid option with a missing argument is to set the has_arg field of the options struct to optional_argument, and then to test manually for an argument. Then getopt_long() will only return a value of '?' when there is an invalid option, and you can check to see if a specified option has an argument by looking in optarg. Here is an example:

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

int main(int argc, char *argv[])
{
int i, opt;
int index = -1;

static struct option long_options[] = {
{"mode", optional_argument, NULL, 'm'},
{0, 0, 0, 0}
};

/* suppress error messages */
//opterr = 0;

while ((opt = getopt_long(argc, argv, "", long_options, &index)) != -1) {
if (opt == '?') {
/* do something, perhaps: */
//printf("Invalid option \n");
// exit(EXIT_FAILURE);
}
if (opt == 'm' && optarg == NULL) {
printf("Missing argument in '--mode' option\n");
exit(EXIT_FAILURE);
}
}

return 0;
}

Using getopt in C with non-option arguments

getopt sets the optind variable to indicate the position of the next argument.

Add code similar to this after the options loop:

if (argv[optind] == NULL || argv[optind + 1] == NULL) {
printf("Mandatory argument(s) missing\n");
exit(1);
}

Edit:

If you want to allow options after regular arguments you can do something similar to this:

while (optind < argc) {
if ((c = getopt(argc, argv, "i:d:btw:h:s:")) != -1) {
// Option argument
switch (c) {
case 'i': {
i = (int)atol(optarg);
}
case 'd': {
d = (int)atol(optarg);
}
case 'b':
buf = 1;
break;
case 't':
time = 1;
break;
case 'w':
w = (int)atol(optarg);
break;
case 'h':
h = (int)atol(optarg);
break;
case 's':
s = (int)atol(optarg);
break;
default:
break;
}
else {
// Regular argument
<code to handle the argument>
optind++; // Skip to the next argument
}
}


Related Topics



Leave a reply



Submit