getopt无法检测选项缺少参数的情况

18

我有一个程序需要接收不同的命令行参数。为了简化问题,我们假设它接收3个标志:-a-b-c,并使用以下代码解析我的参数:

    int c;
    while((c =  getopt(argc, argv, ":a:b:c")) != EOF)
    {
        switch (c)
        {
             case 'a':
                 cout << optarg << endl;
                 break;
             case 'b':
                 cout << optarg << endl;
                 break;
             case ':':
                 cerr << "Missing option." << endl;
                 exit(1);
                 break;
        }
    }

注意:在flag后面加上参数a和b。

但是如果我以以下方式调用程序,会遇到问题:

./myprog -a -b parameterForB

如果我忘记了parameterForA参数,那么parameterForA(由optarg表示)将返回-b,parameterForB被视为没有参数的选项,并且optind设置为parameterForB在argv中的索引。

在这种情况下期望的行为是,在找不到-a的参数时返回':',并且将Missing option.打印到标准错误输出。但是,只有在-a是传递给程序的最后一个参数时才会发生这种情况。

我猜问题是:有没有办法让getopt()假定没有选项以-开头?

5个回答

12
请参见POSIX标准定义中对getopt的说明。其中写道:

如果在option-argument缺失时检测到它(即未提供option-argument),则会根据optstring的第一个字符返回冒号字符 ( ':' ) 或问号字符 ( '?' )。

至于这种情况的检测方法如下:
  1. 如果所选项是argv指向的字符串中的最后一个字符,则optarg将包含argv的下一个元素,并且optind将增加2。如果optind的值大于argc,则表示缺少option-argument,getopt()将返回错误指示。
  2. 否则,optarg将指向该元素中选项字符后面的字符串,而optind将增加1。
看起来getopt并不像你想要的那样运行,所以你需要自己实现检查。幸运的是,你可以通过检查*optarg并自己更改optind来实现。
int c, prev_ind;
while(prev_ind = optind, (c =  getopt(argc, argv, ":a:b:c")) != EOF)
{
    if ( optind == prev_ind + 2 && *optarg == '-' ) {
        c = ':';
        -- optind;
    }
    switch ( …

这样做的额外好处是实际上允许以“-”开头的参数——它们只是不能在空格之前。 (例如my_prog -x-a - Potatoswatter

7

8
我不是。我需要使用 getopt。 - finiteloop

4

完全透明:我对这个问题不是专家。

这个例子来自gnu.org,可能会有用。它似乎处理了在没有提供预期参数的情况下出现'?'字符的情况:

while ((c = getopt (argc, argv, "abc:")) != -1)
    switch (c)
    {
       case 'a':
         aflag = 1;
         break;
       case 'b':
         bflag = 1;
         break;
       case 'c':
         cvalue = optarg;
         break;
       case '?':
         if (optopt == 'c')
           fprintf (stderr, "Option -%c requires an argument.\n", optopt);
         else if (isprint (optopt))
           fprintf (stderr, "Unknown option `-%c'.\n", optopt);
         else
           fprintf (stderr,
                    "Unknown option character `\\x%x'.\n",
                    optopt);
         return 1;
       default:
         abort ();
    }

更新: 也许以下方法可以解决问题?

while((c =  getopt(argc, argv, ":a:b:c")) != EOF)
{
    if (optarg[0] == '-')
    {
        c = ':';
    }
    switch (c)
    {
        ...
    }
}

1
在调用getopt()时,我字符串开头的':'告诉它:如果已知参数需要一个参数,就返回一个':'。此外,我还表明“只有在-a是传递给程序的最后一个参数时,<期望的行为>才会发生。”这正是你的例子中的情况。 - finiteloop
@segfault:请注意,对于不带参数的选项,该代码会产生未定义的结果。实现可以将“optarg”推进到下一个“argv”条目,因为如果不使用,则“optarg”未定义。 - Potatoswatter
@Potatoswatter:听起来有点不妙。你是在说对optarg[0]的调用未定义吗?optarg必须在某处声明,否则代码将无法编译。if语句是否应该在读取optarg[0]之前先检查(optarg != NULL)呢? - e.James
@James:请查看我回答中的链接,了解有关optarg的所有保证。如果optarg没有定义值,我不会指望optarg[0]能正常工作。请查看我的代码,以获得我认为最好的解决方法。 - Potatoswatter
看起来安全多了。做得好。 @segfault:我会把采纳答案改成Potatoswatter的。 - e.James
显示剩余2条评论

2
作为Boost-free项目的替代方案,我有一个简单的仅包含头文件的C++包装器,用于getopt(根据BSD 3-Clause许可证):https://github.com/songgao/flags.hh 从存储库中的example.cc中获取:
#include "Flags.hh"

#include <cstdint>
#include <iostream>

int main(int argc, char ** argv) {
  uint64_t var1;
  uint32_t var2;
  int32_t var3;
  std::string str;
  bool b, help;

  Flags flags;

  flags.Var(var1, 'a', "var1", uint64_t(64), "This is var1!");
  flags.Var(var2, 'b', "var2", uint32_t(32), "var2 haahahahaha...");
  flags.Var(var3, 'c', "var3", int32_t(42), "var3 is signed!", "Group 1");
  flags.Var(str, 's', "str", std::string("Hello!"), "This is a string, and the description is too long to fit in one line and has to be wrapped blah blah blah blah...", "Group 1");
  flags.Bool(b, 'd', "bool", "this is a bool variable", "Group 2");

  flags.Bool(help, 'h', "help", "show this help and exit", "Group 3");

  if (!flags.Parse(argc, argv)) {
    flags.PrintHelp(argv[0]);
    return 1;
  } else if (help) {
    flags.PrintHelp(argv[0]);
    return 0;
  }

  std::cout << "var1: " << var1 << std::endl;
  std::cout << "var2: " << var2 << std::endl;
  std::cout << "var3: " << var3 << std::endl;
  std::cout << "str:  " << str << std::endl;
  std::cout << "b:    " << (b ? "set" : "unset") << std::endl;

  return 0;
}

1

有相当多不同版本的 getopt,所以即使您可以让它在一个版本上工作,可能还会有至少五个其他版本无法使用。除非您有压倒性的理由使用 getopt,否则我建议考虑其他东西,比如 Boost.Program_options


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