0%

C程序的命令行参数处理函数getopt

背景

工作中,存在着这么一个问题。开发提供给测试的二进制版本,都是记录在一个excel表中,保存着对应版本的提测日期和文件的Md5值、以及对应的svn标签信息。但是,文件这种东西,是需要靠人来填写的。许多临时的版本,就没有记录了。经常出现测试的文件找不到对应的记录,不知道是何时提供、解决了什么问题。 那么,就有这么个需求,把对应的信息保存到二进制文件,在编译的时候,获取编译的时间,svn版本号,编译机器的用户名(一般可以对应到开发人员)等信息,自动生成源代码文件,记入编译文件。然后,还需要提供一种方式读取这些信息,让二进制文件支持参数处理是一个不错的方式。

之前代码中有一些可变的配置也是可以通过运行时入参实现的,已经有对参数的处理代码了,不过是通过手动解析argv入参的,方式比较粗暴。远没有系统提供的getopt函数优雅,我们要做一个有追求的程序员,改!

自动获取编译信息

首先在makefile中通过执行shell脚本把编译信息获取到生成信息文件。 这里自动生成了一个version.h文件。

1
2
3
4
5
6
7
8
9
10
11
12
main: version.h main.c
gcc main.c -o main

# 生成version.h文件
version.h:
sh ./genversion.sh

.PHONY: clean

clean:
@rm -fr main version.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#!/bin/sh

FILENAME=./version.h

echo '#ifndef _VERSION_H_'>$FILENAME
echo '#define _VERSION_H_'>>$FILENAME


build_date=`date +"%k:%M:%S %m-%d-%Y"`



echo "#define BUILD_DATE \"${build_date}\"">>$FILENAME
echo "#define BUILD_LINUX_USER \"${USER}\"">>$FILENAME


echo '#endif'>>$FILENAME


命令行参数处理

头文件及内容说明

1
2
3
4
#include <getopt.h>  
// linux 一般在系统的 /usr/include/getopt.h
// mac 在 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/getopt.h

contents:

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

#ifndef _GETOPT_H_
#define _GETOPT_H_

#include <sys/cdefs.h>
#include <unistd.h>

/*
* GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension.
* getopt() is declared here too for GNU programs.
*/
#define no_argument 0
#define required_argument 1
#define optional_argument 2

struct option {
/* name of long option */
const char *name;
/*
* one of no_argument, required_argument, and optional_argument:
* whether option takes an argument
*/
int has_arg;
/* if not NULL, set *flag to val when option found */
int *flag;
/* if flag not NULL, value to set *flag to; else return value */
int val;
};

__BEGIN_DECLS
int getopt_long(int, char * const *, const char *,
const struct option *, int *); // 只处理 --prefix 和短选项
int getopt_long_only(int, char * const *, const char *,
const struct option *, int *); // 处理 --prefix , -prefix 和短选项
#ifndef _GETOPT
#define _GETOPT
int getopt(int, char * const [], const char *) __DARWIN_ALIAS(getopt); // 只处理 -a 短选项

extern char *optarg; /* getopt(3) external variables */ // 指向当前选项的参数 如果没有则为NULL
extern int optind, opterr, optopt;

/**
optind —— 再次调用 getopt() 时的下一个 argv指针的索引。
optopt —— 最后一个未知选项。
opterr ­—— 如果不希望getopt()打印出错信息,则只要将全域变量opterr设为0即可。
*/

#endif
#ifndef _OPTRESET
#define _OPTRESET
extern int optreset; /* getopt(3) external variable */
#endif
__END_DECLS

#endif /* !_GETOPT_H_ */

option结构

用来配置长选项,可以设置对应的短选项,是否有参数。 短选项最好添加到optstring里

optstring

1
2
3
4
char *optstring = “ab:c::”;
单个字符a 表示选项a没有参数 格式:-a即可,不加参数
单字符加冒号b: 表示选项b有且必须加参数 格式:-b 100或-b100,但-b=100错
单字符加2冒号c:: 表示选项c可以有,也可以无 格式:-c200 其它格式错误

返回值

如果所有命令行选项都解析完毕,返回 -1;
如果选项成功找到,返回选项字母(长选项返回对应短字母);如果遇到选项字符不在 optstring 中,且在长选项也找不到,返回字符’?’; 如果遇到丢失参数,那么返回值依赖于 optstring 中第一个字符,如果第一个字符是 ‘:’ 则返回’:’,否则返回’?’并提示出错误信息。

针对长选项,如果不存在对应的短字母,可以参考使用 option_index来解析。

示例代码:

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>

#define NO_LETTER (1)
#define INVALID_VALUE (0xFFFFFFFF)

typedef enum {
reqargIndex = 0,
optargIndex,
noargIndex,
noletterIndex,
nullIndex
} option_index_t;

const struct option long_options[] =
{
{"reqarg", required_argument,NULL, 'r'},
{"optarg", optional_argument,NULL, 'o'},
{"noarg", no_argument, NULL,'n'},
{"noletter", no_argument, NULL, NO_LETTER },
{NULL, 0, NULL, NO_LETTER},
};

int a_val = INVALID_VALUE;
int b_val = INVALID_VALUE;
int c_val = INVALID_VALUE;
int d_val = INVALID_VALUE;
int r_val = INVALID_VALUE;
int o_val = INVALID_VALUE;
int n_val = INVALID_VALUE;
int no_letter = INVALID_VALUE;

// return 1: break, 0:continue
int setOption(int opt, char *optarg, char *argv[], int option_index) {
switch(opt) {
case 'a':
if (optarg) {
a_val = atoi(optarg);
} else {
a_val = 1;
}
break;
case 'b':
{
b_val = atoi(optarg);
}
break;
case 'c':
c_val = atoi(optarg);
break;
case 'd':
d_val = 1;
if (optarg) {
printf("warnning:option d no require argument. ignore value %s\n", optarg);
}
break;
case 'r':
{
r_val = atoi(optarg);
}
break;
case 'o':
if (optarg) {
o_val = atoi(optarg);
} else {
o_val = 1;
}
break;
case 'n‘:
n_val = 1;
if (optarg) {
printf("warnning:option n no require argument. ignore value %s\n", optarg);
}
break;
case NO_LETTER:
{
switch(option_index) {
case noletterIndex:
no_letter = 1;
break;
case nullIndex:

break;
default:
break;
}
}
break;
default:

return 1;
}

return 0;
}

#define showV(x) printf(#x"=%d\n", x)

void showValues() {
printf("=========\n");
showV(a_val);
showV(b_val);
showV(c_val);
showV(d_val);
showV(r_val);
showV(o_val);
showV(n_val);
showV(no_letter);
printf("=========\n");

}

int main(int argc, char *argv[])
{
int opt;
int option_index = 0;
char *string = "a::b:c:dr:o::n";

//initValues();
while((opt =getopt_long_only(argc,argv,string,long_options,&option_index))!= -1)
{
printf("opt = %c\t\t", opt);
printf("optarg = %s\t\t",optarg);
printf("optind = %d\t\t",optind);
printf("argv[optind] =%s\t\t", argv[optind]);
printf("option_index = %d\n",option_index);

if (setOption(opt, optarg, argv, option_index)) {
showValues();
return -1;
}
}

showValues();

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

Keep:syscall keep$ ./a -reqarg 3 # 匹配长选项 带参数 -
opt = r optarg = 3 optind = 3 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a --reqarg 3 # 匹配长选项 带参数 --
opt = r optarg = 3 optind = 3 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a --reqarg # 不匹配长选项 带参数 --
a: option `--reqarg' requires an argument
opt = ? optarg = (null) optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a --optarg # 匹配长选项 可选参数 --
opt = o optarg = (null) optind = 2 argv[optind] =(null) option_index = 1
Keep:syscall keep$ ./a --optarg 9 # 不匹配长选项 可选参数 --
opt = o optarg = (null) optind = 2 argv[optind] =9 option_index = 1
Keep:syscall keep$ ./a --optarg=9 # 匹配长选项 可选参数 --
opt = o optarg = 9 optind = 2 argv[optind] =(null) option_index = 1
Keep:syscall keep$ ./a -optarg=9 # 匹配长选项 可选参数 -
opt = o optarg = 9 optind = 2 argv[optind] =(null) option_index = 1
Keep:syscall keep$ ./a -noarg 3 # 匹配长选项 无参数 - 跳过3
opt = n optarg = (null) optind = 2 argv[optind] =3 option_index = 2
Keep:syscall keep$ ./a -noarg 3 -optarg=9
opt = n optarg = (null) optind = 2 argv[optind] =3 option_index = 2
opt = o optarg = 9 optind = 4 argv[optind] =(null) option_index = 1
Keep:syscall keep$ ./a -a999 # 匹配短选项 可选参数 -
opt = a optarg = 999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -a=999 # 匹配短选项 可选参数 错误 -
opt = a optarg = =999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -b999 # 匹配短选项 带参数 -
opt = b optarg = 999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -b=999 # 匹配短选项 带参数 错误 -
opt = b optarg = =999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -a=999
opt = a optarg = =999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$
Keep:syscall keep$ ./a -b 999
opt = b optarg = 999 optind = 3 argv[optind] =(null) option_index = 0
Keep:syscall keep$
Keep:syscall keep$ ./a -o=999
opt = o optarg = =999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -o999
opt = o optarg = 999 optind = 2 argv[optind] =(null) option_index = 0
Keep:syscall keep$ ./a -o999 -n
opt = o optarg = 999 optind = 2 argv[optind] =-n option_index = 0
opt = n optarg = (null) optind = 3 argv[optind] =(null) option_index = 0
Keep:syscall keep$

缺点

使用getopt_long_only时,会自动最长前缀匹配,存在唯一长选项时,即使没有敲全命令 也认为是匹配上了。

1
2
3
4

./a -hel 会自动匹配到help选项


使用getopt_long时,会自动按字符依次匹配。

1
2
3
4

./a -hel 分配按序匹配 h , e, l 三个字符


代码参考:

https://github.com/fishmwei/blog_code/tree/master/getopt

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。