getopt 不将可选参数解析为参数

Posted

技术标签:

【中文标题】getopt 不将可选参数解析为参数【英文标题】:getopt does not parse optional arguments to parameters 【发布时间】:2010-11-06 08:50:12 【问题描述】:

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

【问题讨论】:

我在这里记录这个答案,所以其他人不需要用头撞墙。 【参考方案1】:

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

【讨论】:

请注意,Perl 的 Getopt::Long 模块没有相同的要求。 Boost.Program_options 可以。 哇,这太糟糕了。我在使用常规 getopt() 时遇到了同样的问题,当使用 optstring“a::”时,只有在选项和参数之间有零空格(例如 '-afoo')时才会设置 optarg 至少对于我的短选项,如-a-a=300,我需要添加一个if (optarg[0] == '=') memmove(optarg, optarg+1, strlen(optarg)); 以去除=。否则,我在optarg 中总是会有=300。或者我需要这样称呼它:-a300 - 我认为这很丑陋。无论如何感谢您的回答,它对我帮助很大! 在这种情况下最好不要使用可选参数。这是错误还是功能?现在我有一种冲动./respond --blame=glibc 我想投反对票,但后来意识到您的回答很棒;这只是我想反对的行为。谢谢!【参考方案2】:

手册页当然没有很好地记录它,但源代码有一点帮助。

简而言之:您应该执行以下操作(尽管这可能有点过于迂腐):

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 之前的 cmets:

...

如果getopt 找到另一个选项字符,则返回该字符, 更新 optindnextchar 以便下次调用 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[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;

【讨论】:

这很好用,谢谢。不确定您从哪里获得 optindex;对我来说,它被称为 (extern int) optind。 第二个代码示例有错误,应该是optind而不是optindex 看起来代码应该维护 optindex。否则,它将始终指向 0。我们需要为每个选项提前 optindex。【参考方案3】:

我最近自己也遇到了这个问题。我得出了与 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 中使用。

【讨论】:

【参考方案4】:

我也遇到了同样的问题,来到这里。然后我意识到了这一点。 您没有太多 "optional_argument" 的用例。如果需要选项,则从程序逻辑中检查,如果选项是可选的,则无需执行任何操作,因为在 getopt 级别,所有选项都是可选的,它们不是强制性的,因此没有“可选参数”的用例。希望这会有所帮助。

ps:对于上面的例子,我认为正确的选项是 --praise --praise-name "名字" --blame --blame-name "名字"

【讨论】:

以上是关于getopt 不将可选参数解析为参数的主要内容,如果未能解决你的问题,请参考以下文章

使用 getopts 读取作为最终位置放置的一个可选参数

定义后无法将可选参数传递给express

在bash中使用getopts来获取可选的输入参数[重复]

spring 将可选查询参数映射到 sql 准备语句

是否有必要在将可选参数传递给另一个可选参数之前检查它?

Getopt可选参数?