getopt не анализирует необязательные аргументы для параметров

В C getopt_long не анализирует необязательные аргументы для параметров параметров командной строки.

Когда я запускаю программу, необязательный аргумент не распознается, как в примере ниже.

$ ./respond --praise John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame
You suck !

Вот тестовый код.

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

int main(int argc, char ** argv )
{
    int getopt_ret, option_index;
    static struct option long_options[] = {
               {"praise",  required_argument, 0, 'p'},
               {"blame",  optional_argument, 0, 'b'},
               {0, 0, 0, 0}       };
    while (1) {
        getopt_ret = getopt_long( argc, argv, "p:b::",
                                  long_options,  &option_index);
        if (getopt_ret == -1) break;

        switch(getopt_ret)
        {
            case 0: break;
            case 'p':
                printf("Kudos to %s\n", optarg); break;
            case 'b':
                printf("You suck ");
                if (optarg)
                    printf (", %s!\n", optarg);
                else
                    printf ("!\n", optarg);
                break;
            case '?':
                printf("Unknown option\n"); break;
        }
    } 
    return 0;
}

5 ответов

Решение

Хотя это не упомянуто в документации glibc или на справочной странице getopt, для необязательных аргументов параметров командной строки длинного стиля требуется знак равенства (=). Пробел, отделяющий необязательный аргумент от параметра, не работает.

Пример запуска с тестовым кодом:

$ ./respond --praise John
Kudos to John
$ ./respond --praise=John
Kudos to John
$ ./respond --blame John
You suck !
$ ./respond --blame=John
You suck , John!

Страница man, конечно, не очень хорошо документирует это, но исходный код немного помогает.

Вкратце: вы должны сделать что-то вроде следующего (хотя это может быть немного чрезмерно педантичным):

if(   !optarg
   && optind < argc // make sure optind is valid
   && NULL != argv[optind] // make sure it's not a null string
   && '\0' != argv[optind][0] // ... or an empty string
   && '-' != argv[optind][0] // ... or another option
  ) {
  // update optind so the next getopt_long invocation skips argv[optind]
  my_optarg = argv[optind++];
}
/* ... */

Из комментариев, предшествующих _getopt_internal:

...

Если getopt находит другой вариант символа, он возвращает этот символ, обновляя optind а также nextchar так что следующий звонок getopt можно возобновить сканирование с помощью следующей опции символа или ARGV-элемента.

Если больше нет опционных символов, getopt возвращает -1. затем optind это индекс в ARGV первого ARGV-элемента, который не является опцией. (ARGV-элементы были переставлены так, что те, которые не являются опциями, теперь идут последними.) <-- a note from me: if the 3rd argument to getopt_long starts with a dash, argv will not be permuted

...

Если за символом в OPTSTRING следует двоеточие, это означает, что он хочет получить аргумент arg, поэтому следующий текст в том же ARGV-элементе или текст следующего ARGV-элемента возвращается в optarg, Две двоеточия означают опцию, которая требует необязательного аргумента; если в текущем ARGV-элементе есть текст, он возвращается в optarg в противном случае optarg установлен на ноль.

...

... хотя вы должны читать между строк. Следующее делает то, что вы хотите:

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

int main(int argc, char* argv[] ) {
  int getopt_ret;
  int option_index;
  static struct option long_options[] = {
      {"praise",  required_argument, 0, 'p'}
    , {"blame",  optional_argument, 0, 'b'}
    , {0, 0, 0, 0}
  };

  while( -1 != ( getopt_ret = getopt_long(  argc
                                          , argv
                                          , "p:b::"
                                          , long_options
                                          , &option_index) ) ) {
    const char *tmp_optarg = optarg;
    switch( getopt_ret ) {
      case 0: break;
      case 1:
        // handle non-option arguments here if you put a `-`
        // at the beginning of getopt_long's 3rd argument
        break;
      case 'p':
        printf("Kudos to %s\n", optarg); break;
      case 'b':
        if(   !optarg
           && NULL != argv[optindex]
           && '-' != argv[optindex][0] ) {
          // This is what makes it work; if `optarg` isn't set
          // and argv[optindex] doesn't look like another option,
          // then assume it's our parameter and overtly modify optindex
          // to compensate.
          //
          // I'm not terribly fond of how this is done in the getopt
          // API, but if you look at the man page it documents the
          // existence of `optarg`, `optindex`, etc, and they're
          // not marked const -- implying they expect and intend you
          // to modify them if needed.
          tmp_optarg = argv[optindex++];
        }
        printf( "You suck" );
        if (tmp_optarg) {
          printf (", %s!\n", tmp_optarg);
        } else {
          printf ("!\n");
        }
        break;
      case '?':
        printf("Unknown option\n");
        break;
      default:
        printf( "Unknown: getopt_ret == %d\n", getopt_ret );
        break;
    }
  }
  return 0;
}

Я недавно сам столкнулся с этой проблемой. Я пришел к решению, аналогичному тому, которое предложили Брайан Ванденберг и Хейстэк. Но чтобы улучшить читаемость и избежать дублирования кода, вы можете обернуть все это в макрос, как показано ниже:

      #define OPTIONAL_ARGUMENT_IS_PRESENT \
    ((optarg == NULL && optind < argc && argv[optind][0] != '-') \
     ? (bool) (optarg = argv[optind++]) \
     : (optarg != NULL))

Макрос можно использовать так:

      case 'o': // option with optional argument
    if (OPTIONAL_ARGUMENT_IS_PRESENT)
    {
        // Handle is present
    }
    else
    {
        // Handle is not present
    }
    break;

Если вам интересно, вы можете узнать больше о том, как это решение работает, в моем блоге:https://cfengine.com/blog/2021/optional-arguments-with-getopt-long/

Это решение протестировано и - на момент написания этой статьи - в настоящее время используется в CFEngine.

Я тоже столкнулся с той же проблемой и пришел сюда. Тогда я понял это. У вас не так много вариантов использования "option_argument" . Если требуется опция, вы проверяете ее в логике программы, если опция необязательна, вам не нужно ничего делать, потому что на уровне getopt все опции являются необязательными, они не являются обязательными, поэтому не существует варианта использования "option_argument" . Надеюсь это поможет.

PS: для приведенного выше примера, я думаю, правильные параметры --praise --praise-name "name" --blame --blame-name "name"

Если вы напишите аргумент рядом с параметром без пробела, ни то, ни другое не сработает. Например:

$ ./respond --blameJohn
You suck John!
Другие вопросы по тегам