getopt不解析参数的可选参数

54

在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;
}

2
我在这里记录答案,以便其他人不必撞墙。 - hayalci
4个回答

100

虽然在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!

2
请注意,Perl的Getopt::Long模块不具有相同的要求。Boost.Program_options有此要求。 - Rob Kennedy
19
哇,这太糟糕了。我在使用普通的getopt()和使用" a:: "选项字符串时遇到了同样的问题,只有在选项和参数之间没有空格的情况下,例如"-afoo",optarg才会被设置。 - SiegeX
3
至少对于像 -a-a=300 这样的短选项,我需要添加一个 if (optarg[0] == '=') {memmove(optarg, optarg+1, strlen(optarg));} 来去掉等号 =。否则,optarg 中总是会有 =300。或者我需要这样调用它:-a300 - 我觉得这样很丑陋。无论如何,感谢你的答复,它对我帮助很大! - mozzbozz
4
在这种情况下最好不要使用可选参数。这是一个错误还是一个特性?现在我有一种冲动,想要执行 ./respond --blame=glibc - petersohn
2
我本想给你的回答点个踩,但后来意识到它很棒;只是我想要点踩的那种行为。谢谢! - lmat - Reinstate Monica
显示剩余3条评论

18
男士页面确实没有很好地记录它,但源代码有所帮助。
简要来说:你应该做以下操作(尽管这可能有点过于迂腐):
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发现另一个选项字符,它将返回该字符, 更新optindnextchar,以便下一次调用getopt 可以使用以下选项字符或ARGV元素恢复扫描。

如果没有更多的选项字符,则getopt返回-1。 然后optind是ARGV中第一个不是选项的ARGV元素的索引。 (ARGV元素已被重排, 使那些不是选项的元素现在出现在最后。) <-- 我的注释: 如果getopt_long的第3个参数以破折号开头,argv将不会被重排

...

如果OPTSTRING中的字符后跟着冒号,那意味着它想要一个参数, 因此同一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[optind]
           && '-' != argv[optind][0] ) {
          // This is what makes it work; if `optarg` isn't set
          // and argv[optind] doesn't look like another option,
          // then assume it's our parameter and overtly modify optind
          // 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`, `optind`, etc, and they're
          // not marked const -- implying they expect and intend you
          // to modify them if needed.
          tmp_optarg = argv[optind++];
        }
        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;
}

1
这个很好用,谢谢。不确定你从哪里得到了optindex; 对我来说它被称为(extern int) optind。 - apanloco
1
第二个代码示例中有一个错误,应该是 optind 而不是 optindex - Sebastian Schrader
看起来代码应该保持optindex。否则,它将始终指向0。我们需要为每个选项提前optindex。 - Howard Shane

2

我最近也遇到了这个问题。我得出的解决方案与Brian Vandenberg和Haystack提出的类似。但为了提高可读性并避免代码重复,您可以将其全部包装在如下的宏中:

#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中使用。

1
我也遇到了同样的问题并来到这里。然后我意识到这一点。 你没有太多使用"optional_argument"的用例。如果一个选项是必需的,你需要从程序逻辑中检查,如果一个选项是可选的,那么你不需要做任何事情,因为在getopt级别上所有选项都是可选的,它们不是强制性的,所以不存在"optional_argument"的用例。希望这可以帮助你。
附:对于上面的例子,我认为正确的选项是 --praise --praise-name "name" --blame --blame-name "name"

1
“optional_argument” 的用例不是很多——哦,但我们确实有。想象一下我们有 $ whip --spank user5081924$ whip --spank。第一个会打屁股的是特定用户,第二个会打屁股的是调用者自己。如果没有可选参数,你只能选择打自己或者打别人。 (我想也可以有 --spank-self--spank-other,但为什么要复杂化呢。) - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接