命令行参数的处理 gflags getopt getopt_long

命令行参数的处理 gflags getopt getopt_long的简单使用与对比

getopt getopt_long

getopt是简单的命令行处理函数。而getopt_long则复杂一些,功能也更加强大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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()用来分析命令行参数。参数argc和argv分别代表参数个数和内容,跟main()函数的命令行参数是一样的。参数 optstring为选项字符串, 告知 getopt()可以处理哪个选项以及哪个选项需要参数,如果选项字符串里的字母后接着冒号“:”,则表示还有相关的参数,全域变量optarg 即会指向此额外参数。如果在处理期间遇到了不符合optstring指定的其他选项getopt()将显示一个错误消息,并将全域变量optarg设为”?“字符,如果不希望getopt()打印出错信息,则只要将全域变量opterr设为0即可。
optarg 指向当前选项参数(如果有)的指针。
optind 再次调用getopt()时的下一个argv指针的索引。
opterr 是否将错误信息输出到stderr的标志,置为0表示不输出。默认输出。
optopt 最后一个未知选项。
getopt optstring参数详解:

  1. 单个字符,表示选项。
  2. 单个字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。该参数的指针赋给optarg。
  3. 单个字符后跟两个冒号::表示该选项后可以跟一个参数。也可以不跟。如果跟一个参数,参数必须紧跟在选项后不能以空格隔开。该参数的指针赋给optarg。

getopt调用返回的是解析出的字符的选项。返回-1表示解析完毕。
简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <unistd.h>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{

for(auto i=0; i<argc; ++i)
{
cout<<"argv "<<i<<" "<<argv[i]<<endl;
}

int ch;
cout<<"optind:"<<optind<<" opterr:"<<opterr<<endl;
while((ch = getopt(argc, argv, "a:bcde::g")) != -1)
{
cout<<"optind:"<<optind<<endl;
switch(ch)
{
case 'a':
cout<<"option a "<<optarg<<endl;
break;
case 'b':
cout<<"option b "<<endl;
cout<<"optarg is NULL :"<<(NULL==optarg)<<endl;
break;
case 'c':
cout<<"option c "<<endl;
break;
case 'd':
cout<<"option d "<<endl;
break;
case 'e':
cout<<"option e "<<optarg<<endl;
break;
case '?':
cout<<"unkown option "<<optarg<<endl;
break;
default:
cout<<"error option"<<endl;
}
}

for(auto i=0; i<argc; ++i)
{
cout<<"after argv "<<i<<" "<<argv[i]<<endl;
}
return 0;
}

编译后使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[CaseZheng@localhost Other]$ ./run -aef gege -ehaha -b gaga -cd
argv 0 ./run
argv 1 -aef
argv 2 gege
argv 3 -ehaha
argv 4 -b
argv 5 gaga
argv 6 -cd
optind:1 opterr:1
optind:2
option a ef
optind:4
option e haha
optind:5
option b
optarg is NULL :1
optind:6
option c
optind:7
option d
after argv 0 ./run
after argv 1 -aef
after argv 2 -ehaha
after argv 3 -b
after argv 4 -cd
after argv 5 gege
after argv 6 gaga

optind初始值为1, 因为argv[0]存储的时程序名。所以从argv[1]开始解析。而optind表示的是下一次开始解析的位置,所以optind初始值为1。
解析a选项,解析出紧跟着的选项参数ef。-a和ef可以紧跟着也可以用空格分割。如果选项参数中有空格需要用” “或’’括起来。例如:

1
2
./run -a "ef ef"
./run -a"ef ef"

解析e选项,解析e选项的参数haha。e选项使用了::,所以选项和选项参数必须紧跟在一起,不能用空格分割。例如:

1
2
./run -e'fefe'
./run -efefe

./run -e fefe 的写法是错误的,fefe无法解析出来。
再解析b c d选项,如果选项后不跟参数可以写在一起,例如-cd
解析途中碰到的gege gaga 则不解析,并将其移动到argv参数尾部。即argv[]中的选项和选项的参数会被放置在数组前面,而optind 会指向第一个非选项和参数的位置。

getopt_long

getopt_long和getopt都是解析命令行参数,但getopt_long支持长选项。

1
2
3
4
5
6
7
8
9
10
int getopt_long(int argc, char * const argv[],
const char *optstring,
const struct option *longopts, int *longindex);

struct option {
const char *name; //长选项名字
int has_arg; //no_argument(或0):选项不携带参数;required_argument(或1):选项需要参数;optional_argument(或2):选项携带可选参数
int *flag; //指定该长选项的返回值。如果flag是NULL,getopt_long会返回val。否则,getopt_long会返回0,且flag指向的值设置为val的值,前提是该选项成功找到了
int val; //当做返回值,或把值加载进flag所指的内存中。
};

longopts用来支持长选项。longopts指向数组的最后一个元素值都设置为0。

  1. 注意相比getopt,使用getopt_long需要加头文件<getopt.h>
  2. getopt_long除了会接受长选项,其他概念和getopt是一样的
  3. 如果使用getopt_long想只接受短选项,设置longopts为NULL即可;如果只想接受长选项,相应地设置optstring为NULL即可
  4. 长选项名是可以使用缩写方式,比如:选项有--file--create,那么输入--c/--cr/--cre等均会被正确识别为create选项
  5. 对于带参数的长选项格式是:--arg=param--arg param
  6. longopts是指向struct option数组的第一个元素的指针,struct option定义在<getopt.h>
  7. longindex如果非NULL,则是返回识别到struct option数组中元素的位置指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <unistd.h>
#include <iostream>
#include <getopt.h>

using namespace std;
//getopt_long示例
int main(int argc, char *argv[])
{

for(auto i=0; i<argc; ++i)
{
cout<<"argv "<<i<<" "<<argv[i]<<endl;
}

int ch;
cout<<"optind:"<<optind<<" opterr:"<<opterr<<endl;
int flag;
const struct option long_options[] = {
{ "append", 1, NULL, 'a' },
{ "help", 0, NULL, 'h' },
{ "verbose", 0, NULL, 'v' },
{ "output", 1, &flag, 'o' },
{ NULL, 0, NULL, 0 }
};
int longindex = 0;
while((ch = getopt_long(argc, argv, "a:hvb:cde::g", long_options, &longindex)) != -1)
{
cout<<"optind:"<<optind<<endl;
cout<<"ch:"<<ch<<endl;
switch(ch)
{
case 0:
cout<<"long option "<<longindex<<" "<<long_options[longindex].name;
if(NULL != optarg)
{
cout<<" arg "<<optarg;
}
cout<<endl;
break;
case 'a':
case 'h':
case 'v':
cout<<"ch "<<(char)ch<<endl;
if(NULL != optarg)
{
cout<<"arg "<<optarg;
}
cout<<endl;
break;
case 'b':
cout<<"option b "<<endl;
cout<<"optarg is NULL :"<<(NULL==optarg)<<endl;
break;
case 'c':
cout<<"option c "<<endl;
break;
case 'd':
cout<<"option d "<<endl;
break;
case 'e':
cout<<"option e "<<optarg<<endl;
break;
case '?':
cout<<"option ? "<<endl;
if(NULL != optarg)
{
cout<<"unkown option "<<optarg<<endl;
}
cout<<"unkown longindex "<<longindex<<endl;
break;
default:
cout<<"error option"<<endl;
}
}

for(auto i=0; i<argc; ++i)
{
cout<<"after argv "<<i<<" "<<argv[i]<<endl;
}
return 0;
}

编译运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[CaseZheng@localhost Other]$ ./run -a fe -hv -cd -b ff -append --output fe
argv 0 ./run
argv 1 -a
argv 2 fe
argv 3 -hv
argv 4 -cd
argv 5 -b
argv 6 ff
argv 7 -append
argv 8 --output
argv 9 fe
optind:1 opterr:1
optind:3
ch:97
ch a
arg fe
optind:3
ch:104
ch h

optind:4
ch:118
ch v

optind:4
ch:99
option c
optind:5
ch:100
option d
optind:7
ch:98
option b
optarg is NULL :0
optind:8
ch:97
ch a
arg ppend
optind:10
ch:0
long option 3 output arg fe
after argv 0 ./run
after argv 1 -a
after argv 2 fe
after argv 3 -hv
after argv 4 -cd
after argv 5 -b
after argv 6 ff
after argv 7 -append
after argv 8 --output
after argv 9 fe

gflags

gflags 是 google 开源的用于处理命令行参数的项目。

简单使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <gflags/gflags.h>

using namespace std;
DEFINE_bool(daemon, true, "run daemon mode");
DEFINE_int32(port, 9000, "program listen port");
DEFINE_string(confpath, "./config.ini", "program config path");
DEFINE_double(dou, 0, "double");

// 定义对 FLAGS_port 的检查函数
static bool ValidatePort(const char* name, int32_t value) {
if (value > 0 && value < 32768) {
return true;
}
printf("Invalid value for --%s: %d\n", name, (int)value);
return false;
}

// 使用全局 static 变量来注册函数,static 变量会在 main 函数开始时就调用,确保参数检查在main函数进入时已经注册。
static const bool port_dummy = gflags::RegisterFlagValidator(&FLAGS_port, &ValidatePort);

int main(int argc, char *argv[])
{
//gflags::RegisterFlagValidator(&FLAGS_port, &ValidatePort);
gflags::ParseCommandLineFlags(&argc, &argv, true);
cout<<"daemon: "<<FLAGS_daemon<<endl;
cout<<"port: "<<FLAGS_port<<endl;
cout<<"configpath: "<<FLAGS_confpath<<endl;
cout<<"dou: "<<FLAGS_dou<<endl;

FLAGS_dou = 9999.8;
cout<<"after dou: "<<FLAGS_dou<<endl;

// 使用 SetCommandLineOption 函数对参数进行设置才会调用检查函数
gflags::SetCommandLineOption("port", "-2");
cout<<"after port: "<<FLAGS_port<<endl;
FLAGS_port = -2;
cout<<"after2 port: "<<FLAGS_port<<endl;

return 0;
}

编译运行:

1
2
3
4
5
6
[CaseZheng@localhost Other]$ g++ -o run CommandLine.cpp -g -lgflags
[CaseZheng@localhost Other]$ ./run -confpath "./config.txt" -daemon="true" --port=111 -dou 1.001
1
111
./config.txt
1.001

gflags支持的类型:

  • DEFINE_bool: boolean
  • DEFINE_int32: 32-bit integer
  • DEFINE_int64: 64-bit integer
  • DEFINE_uint64: unsigned 64-bit integer
  • DEFINE_double: double
  • DEFINE_string: C++ string

如定义: DEFINE_bool(daemon, true, "run daemon mode");
在命令行可以这样访问:

1
2
3
4
5
6
./run -daemon
./run -daemon=true
./run -daemon=1
./run -daemon=false
./run --daemon true
./run --daemon "true"

在程序中可加前缀FLAGS_来访问,例如: cout<<FLAGS_daemon<<endl;
gflags变量可以被修改 FLAGS_daemon = true;

gflags支持从文件中读取命令行参数。
config.flags文件

1
-confpath   "./config.txt" -daemon="true" --port=111 -dou 1.001

命令行使用:

1
2
3
4
5
6
[CaseZheng@localhost Other]$ ./run --flagfile config.flags
1
9000
./config.ini
dou: 0
after dou: 9999.8

外部引用

如果希望在别的文件中使用gflags变量,可以使用DECLARE_type(type为变量类型 int string double等)来声明变量。相当与extern声明变量 例如: DECLARE_bool(daemon);

参数检查

可以注册检查函数值的检查函数。

1
2
3
4
5
6
7
8
9
10
11
12
[CaseZheng@localhost Other]$ ./run -port=99
[CaseZheng@localhost Other]$ ./run -port=-1
Invalid value for --port: -1
ERROR: failed validation of new value '-1' for flag 'port'
daemon: 1
port: 99
configpath: ./config.ini
dou: 0
after dou: 9999.8
Invalid value for --port: -2
after port: 99
after2 port: -2

可以看到命令行输入错误,程序会直接退出。
使用SetCommandLineOption修改参数也会触发参数检测,但如果不合法,程序不退出。如果直接修改参数的值则不会触发参数检测函数。

特殊参数

  • --help 打印定义过的所有参数的帮助信息
  • --version 打印版本信息 通过google::SetVersionString()指定
  • --nodefok 但命令行中出现没有定义的参数时,并不退出(error-exit)
  • --fromenv 从环境变量读取参数值 --fromenv=foo,bar表明要从环境变量读取foo,bar两个参数的值。通过export FLAGS_foo=xxx; export FLAGS_bar=yyy 程序就可读到foo,bar的值分别为xxx,yyy。
  • --tryfromenv--fromenv类似,当参数的没有在环境变量定义时,不退出(fatal-exit)
  • --flagfile 从文件读取参数值,--flagfile=my.conf表明要从my.conf文件读取参数的值。在配置文件中指定参数值与在命令行方式类似,另外在flagfile里可进一步通过--flagfile来包含其他的文件。

对比

  1. getopt getopt_long是Linux标准库中的,可以直接使用,而gflags需要安装。
  2. getopt只支持短选项,getopt_long支持短选项和长选项,getopt_long支持长选项缩写,gflags不支持长选项缩写。
  3. getopt、getopt_long是C函数,通用性强,而gflags使用C++编写,C无法直接使用。
  4. getopt、getopt_long使用不便,gflags使用方便,清晰。