cpp 命令行选项参数获取

本文主要讲解 getopt 和 getopt_long 函数,这两个函数并不是 C++ 标准库的一部分,而是 POSIX 标准的函数,主要用于解析命令行选项,在很多 UNIX 兼容系统中得到了广泛使用。

我们在给某个程序指定选项和参数时,通常是如下形式:

program -a -b value --type typanme

其中横线后面的名称就是选项,选项后面跟着的就是参数,参数有三种情况,可能是不带参数、必须带参数或者参数可选。我们后文所指的短选项和长选项,主要是指使用短名称或者长名称来定义的选项,而参数是指选项后面所跟的变量。

函数声明式如下:

#include <unistd.h>

int getopt(int argc, char * const argv[],
           const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

#include <getopt.h>

int getopt_long(int argc, char * const argv[],
           const char *optstring,
           const struct option *longopts, int *longindex);

int getopt_long_only(int argc, char * const argv[],
           const char *optstring,
           const struct option *longopts, int *longindex);

getopt

其中 getopt 用来解析短选项,getopt_long 和 getopt_long_only 用来解析长选项。

其中 argc 和 argv 就是 main 函数的默认参数,定义与其一致。下面是一个例子

#include <iostream>
#include <unistd.h>

int main(int argc, char *argv[]) {
    int opt;
    while ((opt = getopt(argc, argv, "ab:c::")) != -1) {
        switch (opt) {
            case 'a':
                std::cout << "Option a\n";
                break;
            case 'b':
                std::cout << "Option b with value: " << optarg << "\n";
                break;
            case 'c':
                std::cout << "Option c with value: " << (optarg ? optarg : "none") << "\n";
                break;
            default: // '?'
                std::cerr << "Usage: " << argv[0] << " [-a] [-b value] [-c [value]]\n";
                return 1;
        }
    }
    return 0;
}

在本例中,getopt 函数直接获取 main 函数的参数 argc 和 argv,并指定了第三个参数 ab:c::,它们都是短参数,在执行本程序时,可以通过 [-a] [-b vlaue] [-c [value]] 来运行,使用 [] 扩号的意思为可选,因此:

  • [-a] 表示 -a 选项,它的后面没有冒号,因此该选项不需要参数。
  • [-b value] 表示 -b 选项,它的后面有一个冒号,因此该选项需要一个参数。
  • [-c [vlaue]] 表示 -c 选项,它的后面有两个冒号,因此选项的参数可选。

getopt 函数会返回当前解析到的选项,如果没有解析到,会返回 -1。然后我们根据返回值进入到 switch 的某个分支解析该选项的参数,optarg 是一个全局变量,指向当前的参数。

getopt_long

与 getopt 相比,getopt_long 函数支持解析长选项,我们通常使用 man 手册查看 linux 的相关命令时,也能看到关于长选项的一些应用。在解析长选项之前,我们要定义一下长选项:

#include <iostream>
#include <getopt.h>
#include <cstdlib>

int main(int argc, char *argv[]) {
    int opt;
    int option_index = 0;

    // 定义长选项
    static struct option long_options[] = {
        {"help", no_argument, 0, 'h'},
        {"add", required_argument, 0, 'a'},
        {"delete", required_argument, 0, 'd'},
        {"verbose", no_argument, 0, 'v'},
        {0, 0, 0, 0}
    };

    // 解析选项
    while ((opt = getopt_long(argc, argv, "ha:d:v", long_options, &option_index)) != -1) {
        switch (opt) {
            case 'h':
                std::cout << "Usage: " << argv[0] << " [options]\n"
                          << "Options:\n"
                          << "  -h, --help           Display this help message\n"
                          << "  -a, --add VALUE      Add value\n"
                          << "  -d, --delete VALUE   Delete value\n"
                          << "  -v, --verbose        Enable verbose mode\n";
                exit(EXIT_SUCCESS);
            case 'a':
                std::cout << "Add option with value: " << optarg << "\n";
                break;
            case 'd':
                std::cout << "Delete option with value: " << optarg << "\n";
                break;
            case 'v':
                std::cout << "Verbose mode enabled\n";
                break;
            default:
                std::cerr << "Usage: " << argv[0] << " [-h] [-a value] [-d value] [-v]\n";
                exit(EXIT_FAILURE);
        }
    }

    return 0;
}

在上述例子中,与 getopt 相比,我们给 getopt_long 多传入了两个参数:long_options 和 &option_index。其中 long_options 是一个结构体数组,用于指明所有的长选项配置,而每个结构体的结构如下:

struct option {
    const char *name;
    int         has_arg;
    int        *flag;
    int         val;
};

其中 name 就是长选项的名称,has_arg 表示是否指定参数,getopt.h 头文件提供了一些宏定义:

# define no_argument		0
# define required_argument	1
# define optional_argument	2

它们与我们讲解 getopt 的例子一致,分别对应无冒号、一个冒号和两个冒号三种情况,表示无参数、有参数,参数可选。

第三个成员变量 flag 表示长选项参数的返回形式:

  • flag 为 NULL,getopt_long 在解析到该选项时会返回指定的 val,即第四个成员变量,并将该选项的参数存在 optarg 中。
  • flag 不为 NULL,getopt_long 在解析到该选项时会返回 0,并将该选项的参数存放在 flag 所指的变量上。如果到最后也没有解析到该参数,那么 flag 所指内容将被赋值为 val,即第四个成员变量。

flag 和 val 的配置稍微复杂一些,但搞清楚之后,能够让我们更好的去应用 getopt_long 的能力。这里简单介绍两种我遇到的使用情况:

  1. 对于某个选项,提供一个长名称和一个短名称。

比如参数 help,除了提供长名称 help 之外,还提供短名称 h。在这种情况下,我们可以将 long_options 的结构体中 help 选项的 val 值设置为 h,这样直接根据 getopt_long 的返回值为 h 时来处理该选项。

  1. 对于某个选项,只提供一个长名称。

这种情况可能比较少见,但有时要求用户必须指明长选项名称,会比使用一个无明确意义的短名称更不容易出现错误,而这带来的代价是输入时多打几个字母,这在程序的参数众多时会比较有用,因为常常会忘记某个参数的具体含义。

比如我们要指定一个 scene_type 选项,在使用 --scene_type param 后,getopt_long 会解析到它,并根据 flag 的情况决定返回什么内容,我通常会把 flag 设置为 null,然后 getopt_long 就会返回 val,再根据 val 去对该选项的参数做处理。

例如:

static struct option long_options[] = {
    ...
    {"scene_type", required_argument, 0, 0},
    {"run_type", required_argument, 0, 0},
    ...
    {0, 0, 0, 0}
};

while ((opt = getopt_long(argc, argv, "ha:d:v", long_options, &option_index)) != -1) {
    switch (opt) {
        ...
        case 0:
          const char *option_name = long_options[option_index];
          if (strcmp(option_name, "sceen_type") == 0) {
            ...
          } else if (strcmp(option_name, "run_type") == 0) {

          }
        ...
    }
}

是的,在上面这个例子中,我把两个选项的 val 都设置为了 0,这样 getopt_long 在解析到之后返回 0,然后通过 getopt_long 的第五个参数 option_index 来获取选项的长名称。对这个参数终于出现了,我目前仅仅发现它在这种情况下有用,因为在其他情况下,我们用短参数足矣。option_index 是一个出参,需要我们提前定义好,它表示当前解析的选项在 long_options 中的下标。

getopt_long_only

与 getopt_long 相比,getopt_long_only 支持使用单个横线来指定长选项,就像 -。如果 - 后面的名称与长选项不匹配,但与某个短选项匹配了,那么它会匹配到短选项上。

参考资料

  1. getopt_long(3) linux man page
posted @ 2024-11-18 14:51  kpole  阅读(31)  评论(0编辑  收藏  举报