Почему C getopt_long_only () не устанавливает optopt для неизвестной опции? - PullRequest
0 голосов
/ 18 декабря 2018

Я пытаюсь использовать getopt_long_only() с пользовательскими сообщениями об ошибках.Код показан ниже.Я попытался установить opterr = 0 и использовать двоеточие в начале строки optstring, чтобы отключить встроенные сообщения об ошибках.Я добавил блок кода, контролируемый с помощью логического optoptWorks = true, чтобы попытаться настроить сообщения об ошибках, например, чтобы напечатать сообщение, когда используется неверный параметр, такой как -z.Однако optopt всегда имеет значение 0, и сообщение об ошибке ?, которое я использую, не имеет смысла.Ошибки ':' (в двоеточии) (отсутствующий аргумент, такой как -d) работает нормально для пользовательских сообщений.Отключение встроенных сообщений об ошибках и их обработка в ?, по-видимому, приводит к тому, что optopt всегда устанавливается в 0, поэтому я не могу напечатать параметр, вызывающий оскорбления (-z is not recognized).Я скомпилировал на Debian Linux gcc 4.9.4, а также Cygwin gcc 7.3.0, и оба дали тот же результат.Похоже, getopt_long_only() может не установить optopt правильно или я что-то упустил?Многие примеры в Интернете позволяют обойти это, используя либо встроенные сообщения об ошибках, либо просто используя печать, не сообщая пользователю, какая опция не распознана.

Здесь выводится optoptWorks=false:

$ ./testoptget -z
testoptget: unknown option -- z

-d #            Set the debug level.
-h, --help      Print program usage.
-q              Run in quiet mode (log messages to syslog but not console).
-v, --version   Print program version.

$ ./testoptget -d
testoptget: option requires an argument -- d

-d #            Set the debug level.
-h, --help      Print program usage.
-q              Run in quiet mode (log messages to syslog but not console).
-v, --version   Print program version.

и здесь выводится с optoptWorks=true:

$ ./testoptget -z
[ERROR] Unknown option character '\x0'.

-d #            Set the debug level.
-h, --help      Print program usage.
-q              Run in quiet mode (log messages to syslog but not console).
-v, --version   Print program version.

$ ./testoptget -d
[ERROR] Option '-d' is missing argument.

-d #            Set the debug level.
-h, --help      Print program usage.
-q              Run in quiet mode (log messages to syslog but not console).
-v, --version   Print program version.

Код следующий:

/*
Test program for getopt_long_only
*/

#include <ctype.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int debug = 0; /* Default to not debugging */

/**
Print the program usage.
*/
void usage(void)
{
    /* Print the program name and version */
    printf("\n");
    printf("-d #            Set the debug level.\n");
    printf("-h, --help      Print program usage.\n");
    printf("-q              Run in quiet mode (log messages to syslog but not console).\n");
    printf("-v, --version   Print program version.\n\n");
    exit(0);
}

/**
Parse command line parameters and set data for program.
@param argc number of command line parameters
@param argv list of command line parameters
*/
void parseargs(int argc,char **argv)
{   /*
    See:  https://www.gnu.org/software/libc/manual/html_node/Getopt.html#Getopt
    See:  http://man7.org/linux/man-pages/man3/getopt.3.html

    Because legacy -version and --version need to be supported, use getopts_long_only.
    */

    /*
    The meaning of the following is:
    name - the name of the long option
    has_arg - whether the option has an argument like --arg param or --arg=param
    flag - the numeric value to return (set to "opt" below), if NULL or zero, return "val"
    val - the value to return (set to "opt" below) if "flag" not set, use the one-character equivalent
    */
    static struct option long_options[] = {
        { "help",    no_argument, 0, 'h' }, /* returns as if -v, index not needed */
        { "version", no_argument, 0, 'v' }, /* returns as if -h, index not needed */
        { 0,         0,           0, 0 }    /* last element of array must be zeros */
    };
    int long_index = 0;
    int opt;
    int errorCount = 0;
    /* In <unistd.h>:  external int optind, opterr, optopt */
    bool optoptWorks = false;  /* Apparently optopt gets set to 0 for unknown argument so let the getopt_long_only print the error */
    char optstring[32] = "d:hqv";
    if ( optoptWorks ) {
        /*
        If getopt_long_only works as it is supposed to...
        Set opterr to zero so getopt calls won't print an error - check for errors in '?' return value
        Also use : as first character of optstring to cause : to be used for error handling
        */
        opterr = 0;
        /* Do the following because strcat is not safe on overlapping strings */
        char optstring2[32];
        strcpy(optstring2,optstring);
        strcpy(optstring,":");
        strcat(optstring,optstring2);
    }
    while((opt = getopt_long_only(argc, argv, optstring, long_options, &long_index)) != -1) {
        switch (opt) { /* Will match single character option or long_options val or flag */
            case 'd':
                /* -d #, Set the debug level to the argument value */
                debug = atoi(optarg);
                break;
            case 'h':
                /*
                -h, print the usage and exit
                -help
                --help
                */
                usage();
                exit(0);
                break;
            case 'q':
                /* -q, indicate that messages should not be printed to stdout */
                break;
            case 'v':
                /*
                -v, print the version via standard function,
                -version
                --version
                */
                break;
            case ':':
                /*
                This is an error indicator indicated by : at the start of get_opt_long 3rd argument.
                Handle missing argument, such as -d but no argument.
                */
                fprintf(stderr, "[ERROR] Option '-%c' is missing argument.\n", optopt);
                ++errorCount;
                break;
            case '?':
                /*
                Handle unknown parameters as per getopt man page example.
                "optopt" should contain the offending argument, but perhaps matches last long argument (zero record).
                Note that legacy ? command line parameter is no longer supported.
                */
                if (isprint(optopt)) {
                    /* Printable character so print it in the warning. */
                    if ( optoptWorks ) {
                        fprintf(stderr, "[ERROR] Unknown option '-%c'.\n", optopt);
                    }
                    ++errorCount;
                }
                else {
                    /* Nonprintable character so show escape sequence. */
                    if ( optoptWorks ) {
                        fprintf(stderr, "[ERROR] Unknown option character '\\x%x'.\n", optopt);
                    }
                    ++errorCount;
                }
                break;
        } /* end switch */
    } /* end while */
    if ( errorCount > 0 ) {
        usage();
        exit(1);
    }
}

/**
Main program.
@param argc number of command line parameters
@param argv list of command line parameters
@param arge list of environment variables
*/
int main(int argc,char **argv,char **arge)
{
    /* Parse command arguments */
    parseargs(argc,argv);

    /* Normal program termination */
    return(0);
}

1 Ответ

0 голосов
/ 18 декабря 2018

optopt действительно устанавливается в ноль, когда найдена неизвестная длинная опция, см. здесь .Однако мне кажется, что вы можете использовать optind - 1 в качестве индекса в argv для печати неправильной опции, так как optind увеличивается здесь прямо перед getopt, возвращается '?'.

Насколько я понял, ваша цель просто указать пользовательские сообщения об ошибках.

Также от man getopt_long:

Если первый символ (после любого необязательного '+' или'-' описано выше) optstring является двоеточием (':'), тогда getopt () также не выводит сообщение об ошибке.Кроме того, он возвращает «:» вместо «?»указать отсутствующий параметр аргумента.Это позволяет вызывающему абоненту различать два разных типа ошибок.

Документация, на которую вы ссылались, касается getopt, а не getopt_long_only. man getopt_long_only действительно говорит, что getopt_long() function works like getopt(), но optopt установлен на "символ опции".В случае длинных опций нет «символа опции», но есть «строка опции» (как я бы это назвал) - мне кажется логичным установить optopt как ноль.

Таким образом, в зависимости от начального символав optstring возвращается : или ?, как реализовано здесь и здесь и здесь .

Следующая программаУ вас есть комментарии, удалены, закорочена функция использования, заменено exit на return и добавлена ​​опция печати, нарушающая просто printf("%s", argv[opting - 1]);:

#include <ctype.h>
#include <getopt.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int debug = 0;

void usage(void) { printf("-- insert usage here --\n"); }

void parseargs(int argc,char **argv)
{
    static struct option long_options[] = {
        { "help",    no_argument, 0, 'h' },
        { "version", no_argument, 0, 'v' },
        { 0,         0,           0, 0 }
    };
    int long_index = 0;
    int opt;
    int errorCount = 0;

    optind = 1;

    while((opt = getopt_long_only(argc, argv, ":d:hqv", long_options, &long_index)) != -1) {
        switch (opt) {
            case 'd':
                debug = atoi(optarg);
                break;
            case 'h':
                usage();
                return;
                break;
            case 'q':
                break;
            case 'v':
                break;
            case ':':
                fprintf(stderr, "[ERROR] Option '-%c' is missing argument.\n", optopt);
                ++errorCount;
                break;
            case '?':
                if (optopt == 0) {
                    fprintf(stderr, "[ERROR] Unknown option '%s'.\n", argv[optind - 1]);
                } else {
                    fprintf(stderr, "[ERROR] Error parsing option '-%c'\n", optopt);
                }
                ++errorCount;
                break;
        }
    }
    if ( errorCount > 0 ) {
        usage();
        return;
    }
}

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

#define SIZE(x) (sizeof(x)/sizeof(*x))
    struct {
        int argc;
        char **argv;
    } tests[] = {
        { 2, (char*[]){ argv[0], (char[]){"-z"}, NULL, } },
        { 2, (char*[]){ argv[0], (char[]){"-d"},  NULL, } },
    };

    for (int i = 0; i < SIZE(tests); ++i) {
        printf("\n## test tests[i].argv[1] = %s\n", tests[i].argv[1]);
        parseargs(tests[i].argc, tests[i].argv);
    }

    return 0;
}

Выходы:

## test tests[i].argv[1] = -z
[ERROR] Unknown option '-z'.
-- insert usage here --

## test tests[i].argv[1] = -d
[ERROR] Option '-d' is missing argument.
-- insert usage here --

Если опция указанаустанавливается на "d:hqv" без начального :, затем он попадает в случай ?, т.е.затем программа возвращает:

## test tests[i].argv[1] = -z
./a.out: unrecognized option '-z'
[ERROR] Unknown option '-z'.
-- insert usage here --

## test tests[i].argv[1] = -d
./a.out: option requires an argument -- 'd'
[ERROR] Error parsing option '-d'
-- insert usage here --
...