getopt()函数

在读nemu/src/monitor/monitor.c中遇到了这个函数,原文如下:

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
static inline void parse_args(int argc, char *argv[]) {
const struct option table[] = {
{"batch" , no_argument , NULL, 'b'},
{"log" , required_argument, NULL, 'l'},
{"diff" , required_argument, NULL, 'd'},
{"port" , required_argument, NULL, 'p'},
{"help" , no_argument , NULL, 'h'},
{0 , 0 , NULL, 0 },
};
int o;
while ( (o = getopt_long(argc, argv, "-bhl:d:p:", table, NULL)) != -1) {
switch (o) {
case 'b': batch_mode = true; break;
case 'p': sscanf(optarg, "%d", &difftest_port); break;
case 'l': log_file = optarg; break;
case 'd': diff_so_file = optarg; break;
case 1:
if (img_file != NULL) Log("too much argument '%s', ignored", optarg);
else img_file = optarg;
break;
default:
printf("Usage: %s [OPTION...] IMAGE\n\n", argv[0]);
printf("\t-b,--batch run with batch mode\n");
printf("\t-l,--log=FILE output log to FILE\n");
printf("\t-d,--diff=REF_SO run DiffTest with reference REF_SO\n");
printf("\t-p,--port=PORT run DiffTest with port PORT\n");
printf("\n");
exit(0);
}
}
}

这个函数中使用了getopt_long()函数, 在man 3 getopt并查阅资料后,对这个函数有了初步的了解
#

概要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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);

char *optarg:指向当前选项参数(如果有)的指针。

int optind:下一次调用 getopt() 时的 argv 索引的起始位置。

int opterr:如果非零,getopt() 会在遇到错误时打印错误消息。如果把opterr设置为0, 那么getopt()就不会自动错误报告

getopt()其实是一个提供解析命令行参数的函数,getopt()函数使得程序能够以一种标准的方式来解析命令行选项(如 -a--option 这样的短选项或长选项)。

函数原型

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

argc argv[]:这两个参数直接来自于程序的 main() 函数,代表命令行参数的数量和参数的字符串数组。
optstring:一个包含有效选项字符的字符串。如果一个选项字符后面跟随一个冒号(:),那么表示该选项需要一个参数(例如,-f filename 中的 filename 就是 -f 选项的参数)。 eg: optstring = “abc:d” ,那么说明一共有四个选项-a、-b、-c、-d, 其中c后面跟一个冒号,说明在使用-c选项的时候需要提供一个参数。

使用方式

getopt() 函数在每次调用时都会返回命令行中的下一个选项字符,直到所有选项都被处理完毕,此时返回 -1。处理选项时,如果遇到未定义的选项字符,getopt() 会打印一个错误消息到标准错误并返回 ?。如果选项需要一个参数但未提供参数,根据 optstring 的第一个字符是否为 :,getopt() 返回 ? 或 : 来区分错误类型。

示例代码

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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

while ((opt = getopt(argc, argv, "if:lr")) != -1) {
switch (opt) {
case 'i':
case 'l':
case 'r':
printf("Option: %c\n", opt);
break;
case 'f':
printf("Filename: %s\n", optarg);
break;
case '?':
fprintf(stderr, "Usage: %s [-ilr] [-f filename] args\n", argv[0]);
exit(EXIT_FAILURE);
}
}

for (int index = optind; index < argc; index++)
printf("Non-option argument: %s\n", argv[index]);

return 0;
}

这里提供了四个选项-i、-f、-l、-r, 其中使用-f选项的时候需要提供一个参数, 如果提供的选项没有定义,就会打印Non-option argument

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ffo@debian:~/Cprogramme$ vim test_getopt.c 
ffo@debian:~/Cprogramme$ gcc test_getopt.c
ffo@debian:~/Cprogramme$ ./a.out -i
Option: i
ffo@debian:~/Cprogramme$ ./a.out -f "1121"
Filename: 1121
ffo@debian:~/Cprogramme$ ./a.out -a
./a.out: invalid option -- 'a'
Usage: ./a.out [-ilr] [-f filename] args
ffo@debian:~/Cprogramme$ ./a.out -f
./a.out: option requires an argument -- 'f'
Usage: ./a.out [-ilr] [-f filename] args
ffo@debian:~/Cprogramme$ ./a.out f
Non-option argument: f

由上可以看出getopt()其实就是对命令行参数解析的函数, 同时也可以解析多个选项,like this:

1
2
3
4
5
ffo@debian:~/Cprogramme$ ./a.out -i -f "1121" -a
Option: i
Filename: 1121
./a.out: invalid option -- 'a'
Usage: ./a.out [-ilr] [-f filename] args

我们也可以修改源文件查看optind, oprerr的值:
在打印选项之前添加一行代码:

1
printf("optind: %d ", optind);

在打印错误消息前添加一行

1
printf("optind: %d opterr: %d ", optind, opterr);

再次执行得到:

1
2
3
4
5
6
 ./a.out -i -l -f
optind: 2 Option: i
optind: 3 Option: l
./a.out: option requires an argument -- 'f'
Usage: ./a.out [-ilr] [-f filename] args
optind: 4 opterr: 1

其中有个小问题,我们明明在打印错误消息前先打印了optind,opterr的值,但是为什么记过出来之后却是先打印错误消息在打印这两个变量的值, 这是因为当遇到需要参数但未提供参数的选项(例如 -f)时,getopt 函数的行为会因 opterr 的值而有所不同。在大多数环境中,默认情况下 opterr 被设置为 1,这意味着 getopt 会自动打印错误信息到标准错误输出(stderr)。

这条消息是由 getopt 函数自动产生的,因为 -f 选项缺少必需的参数。在打印了错误消息之后,getopt 函数返回 ‘?’,导致程序执行 case ‘?’ 分支。在这个分支中,尝试打印 optind 和 opterr 的值。

但是,由于错误消息已经被 getopt 自动打印出来了(在进入 case ‘?’ 分支之前),所以看起来像是 Usage 信息和 optind: 2 opterr: 1 的打印顺序颠倒了。

getopt_long()

1
2
3
4
5
6
7
#include <getopt.h>

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

这个函数比getopt()多加了两个参数, 但是原先的参数还是同样的作用。

const struct option *longopts: 指向 option 结构的数组,每个元素定义了一个长选项,包括选项的名称、是否需要参数、一个返回值的指针和短选项字符。数组的最后一个元素必须是全零,作为终结符。

int *longindex: 如果非 NULL,getopt_long 将在此处存储 longopts 数组中当前找到的选项的索引。

option 结构定义

1
2
3
4
5
6
struct option {
const char *name; // 长选项名称
int has_arg; // 选项是否需要参数:no_argument(0)、required_argument(1)或 optional_argument(2)
int *flag; // 如果非 NULL,getopt_long 返回 0,而 *flag 设置为 val 的值;如果 NULL,getopt_long 返回 val
int val; // 与选项相关的值;如果 flag 非 NULL,val 是 *flag 的值,否则 val 是 getopt_long 的返回值
};

使用示例

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
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>

int main(int argc, char *argv[]) {
int c;
int digit_optind = 0;

static struct option long_options[] = {
{"add", required_argument, 0, 'a'},
{"append", no_argument, 0, 'b'},
{"delete", required_argument, 0, 'd'},
{"verbose", no_argument, 0, 'v'},
{"create", required_argument, 0, 'c'},
{"file", required_argument, 0, 'f'},
{0, 0, 0, 0}
};

while (1) {
int option_index = 0;

c = getopt_long(argc, argv, "abc:d:vf:", long_options, &option_index);

if (c == -1)
break;

switch (c) {
case 'a':
case 'b':
case 'v':
printf("Option %c\n", c);
break;
case 'c':
case 'd':
case 'f':
printf("Option %c with argument '%s'\n", c, optarg);
break;
case '?':
break;
default:
printf("?? getopt returned character code 0%o ??\n", c);
}
}

if (optind < argc) {
printf("Non-option ARGV-elements: ");
while (optind < argc)
printf("%s ", argv[optind++]);
printf("\n");
}

exit(EXIT_SUCCESS);
}

执行后会发现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ffo@debian:~/Cprogramme$ ./a.out -a
Option a
ffo@debian:~/Cprogramme$ ./a.out -f "12121"
Option f with argument '12121'
ffo@debian:~/Cprogramme$ ./a.out -add
Option a
Option d with argument 'd'
ffo@debian:~/Cprogramme$ ./a.out --add
./a.out: option '--add' requires an argument
ffo@debian:~/Cprogramme$ ./a.out --add "12121"
Option a
ffo@debian:~/Cprogramme$ ./a.out --verbose
Option v
ffo@debian:~/Cprogramme$

现在我们无论用短选项还是长选项都可以正确解析。