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!