本文主要讲解 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 的能力。这里简单介绍两种我遇到的使用情况:
- 对于某个选项,提供一个长名称和一个短名称。
比如参数 help
,除了提供长名称 help
之外,还提供短名称 h
。在这种情况下,我们可以将 long_options 的结构体中 help 选项的 val 值设置为 h
,这样直接根据 getopt_long 的返回值为 h
时来处理该选项。
- 对于某个选项,只提供一个长名称。
这种情况可能比较少见,但有时要求用户必须指明长选项名称,会比使用一个无明确意义的短名称更不容易出现错误,而这带来的代价是输入时多打几个字母,这在程序的参数众多时会比较有用,因为常常会忘记某个参数的具体含义。
比如我们要指定一个 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 支持使用单个横线来指定长选项,就像 -
。如果 -
后面的名称与长选项不匹配,但与某个短选项匹配了,那么它会匹配到短选项上。