GNU C getopt()、getopt_long() 与 getopt_long_only() 获取命令行参数

1.背景

众所周知,C/C++ 程序的主函数有两个参数。第一个参数是整型,可以获得包括程序名字的参数个数,第二个参数是字符数组指针或字符指针的指针,可以按顺序获得命令行上各个字符串参数。其原型是:

int main(int argc, char *argv[]);
//或者
int main(int argc, char **argv);

如何解析命令行输入的参数呢,可以使用以下几个glibc库函数来实现。

int getopt(int argc, char * const argv[],const char *optstring)
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_long() 既支持短格式也支持长格式选项,getopt_long_only() 用法和getopt_long() 完全一样,唯一的区别在输入长选项的时候可以不用输入双横杠--而使用单横杠-。一般情况下,使用getopt_long() 来完成命令行选项以及参数的获取。

下面将一一介绍三者的具体用法。

2.getopt()

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

功能:获取短格式命令参数。
头文件:#include <unistd.h>
参数说明:
(1)argc:同main函数参数argc相同,表示命令行参数个数;
(2)argv:同main函数参数argv相同,表示命令行参数数组;
(3)optstring:为选项字符串,告知getopt()可以处理哪个选项以及哪个选项需要参数。如果选项字符串里的字母后接着冒号“:”,则表示选项后面必须带有参数,否则报错,这个参数可以和选项连在一起写,也可以用空格隔开。如果字母后跟两个冒号,则表示这个选项的参数是可选的,即可以有参数,也可以没有参数,但要注意有参数时,参数与选项之间不能有空格,否则报错,这一点和一个冒号时是有区别的。

比如给定选项字符串"a🅱️cd::e",对应到命令行就是:

-a [arg] 或 -a[arg](没有空格 )
-b [arg] 或 -b[arg](没有空格 )
-c
-d 或 -d[arg](选项有参数时,必须和选项连在一起写)
-e

返回值:如果一个选项被成功找到,则返回选项字符。如果getopt()遇到未知选项,则返回字符’?’。如果所有命令行选项已被解析,返回-1。如果getopt()遇到选项缺少参数,返回值取决于optstring的第一个字符,如果是’:’,则返回冒号,否则返回’?’。

相关全局变量:
extern char* optarg:保存选项的参数;
extern int optind:记录下一个检索位置;
extern int opterr:如果在处理期间遇到了不符合optstring指定的其他选项,getopt()将显示一个错误消息,并将全局变量optopt设为"?"字符。opterr决定是否将错误信息输出到stderr,为0时表示不输出;
extern int optopt:存放不在选项字符串optstring中的选项。

**注意:**不带参数的选项可以写在一起,比如使用shell命令rm -rf *删除当前目前下的所有文件与目录。-r表示递归删除,-f表示不提示立刻删除,它们两个都不带参数,这时就可以写在一起。

具体示例:

#include <unistd.h>
#include <stdio.h>

int main(int argc, char * argv[])
{
    int ch;
    printf("optind:%d opterr:%d\n",optind,opterr);
    while((ch=getopt(argc,argv,"ab:c:de::"))!=-1)
	{
        printf("optind: %d\n", optind);
        switch (ch) 
        {
			case 'a':
				printf("HAVE option: -a\n");   
                break;
			case 'b':
				printf("HAVE option: -b\n"); 
				printf("The argument of -b is %s\n\n",optarg);
				break;
			case 'c':
				printf("HAVE option: -c\n");
				printf("The argument of -c is %s\n\n",optarg);
				break;
			case 'd':
				printf("HAVE option: -d\n");
				break;
			case 'e':
				printf("HAVE option: -e\n");
				printf("The argument of -e is %s\n\n", optarg);
				break;
			case '?':
                printf("Unknown option: %c\n",(char)optopt);
                break;
        }
	}
}

编译后,命令行执行与输出结果:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out -b test
optind:1 opterr:1
optind: 3
HAVE option: -b
The argument of -b is test

optind 和 opterr 的初始值都为 1,前面提到过 opterr 非零表示产生的错误要输出到 stderr 上。那么 optind的初值为什么是 1 呢?

这就要涉及到main函数的那两个参数了,argc表示参数的个数,argv[]表示每个参数字符串,对于上面的输出argc就为3,argv[]分别为: ./a.out 和 -b 和"test",实际上真正的参数是从第二个-b 开始,也就是argv[1],所以optind的初始值为1。

当执行getopt()函数时,会依次扫描每一个命令行参数(从下标1开始),第一个-b,是一个选项,而且这个选项在选项字符串optstring中有,我们看到b后面有冒号,也就是b后面必须带有参数,而"test"就是他的参数。所以这个命令行是符合要求的。至于执行后optind为什么是3,这是因为optind是下一次进行选项搜索的开始索引,也是说下一次getopt()函数要从argv[3]开始搜索。当然,这个例子argv[3]已经没有了,此时getopt()函数就会返回-1。

再看一个例子:

[dablelv@TENCENT64 ~/test/getopt]$  ./a.out -b "test" -c1234
optind:1 opterr:1
optind: 3
HAVE option: -b
The argument of -b is test

optind: 4
HAVE option: -c
The argument of -c is 1234

对于这个过程会调用三次 getopt() 函数,和第一个输入一样,是找到选项-b和他的参数"test",这时 optind 的值为 3,也就意味着,下一次的 getopt() 要从 argv[3] 开始搜索,所以第二次调用 getopt() 函数,找到选项 -c 和他的参数 1234(选项和参数是连在一起的),由于 -c1234 写在一起,所以他两占一起占用 argv[3],所以下次搜索从 argv[4] 开始,而 argv[4] 为空,这样第三次调用 getopt() 函数就会返回 -1,循环随之结束。

看一个输入错误命令选项的例子:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out -f 123
optind:1 opterr:1
./a.out: invalid option -- 'f'
optind: 2
Unknown option: f

其中./a.out: invalid option -- 'f'就是输出到 stderr 的错误输出。如果把 opterr 设置为 0 那么就不会有这条输出。

再看一个输入错误的例子:

dablelv@TENCENT64 ~/test/getopt]$ ./a.out -zheng
optind:1 opterr:1
./a.out: invalid option -- 'z'
optind: 1
Unknown option: z
./a.out: invalid option -- 'h'
optind: 1
Unknown option: h
optind: 2
HAVE option: -e
The argument of -e is ng

前面提到过不带参数的选项可以写在一起,所以当getopt()找到-z的时候,发现在optstring 中没有,这时候他就认为h也是一个选项,也就是-h和-z写在一起了,依次类推,直到找到-e,发现optstring中有。

最后要说明一下,getopt()会改变argv[]中参数的顺序。经过多次getopt()后,argv[]中的选项和选项的参数会被放置在数组前面,而optind 会指向第一个非选项和参数的位置。看例子:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out zheng -b "test" han -c123 qing
./a.out
zheng
-b
test
han
-c123
qing
----------------
optind:1 opterr:1
optind: 4
HAVE option: -b
The argument of -b is test

optind: 6
HAVE option: -c
The argument of -c is 123

----------------
./a.out
-b
test
-c123
zheng
han
qing

我们看到,被getopt挑出的选项和对应的参数都按顺序放在了数组的前面,而那些既不是选项又不是参数的会按顺序放在后面。而此时optind为4,即指向第一个非选项也非选项的参数,zheng。

#3.getopt_long()

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

有了对 getopt() 了解,对 getopt_long() 的理解相对来说也就比较简单了,因为 getopt_long() 的用法与 getopt() 极其相似,包含了 getopt() 的所有功能,只是增加了对长选项的支持,长选项使用两个横杠--表示。

功能:获取短格式命令参数或长格式命令参数
头文件:header:#include <getopt.h>
参数说明:
(1)argc、argv和optstring:同getopt()的参数,具体不再赘述;
(2)longopts:是定义在<getopt.h>中结构体option实例数组,option定义如下:

struct option
{
	const char *name;    //表示的是长选项名
	int         has_arg; 
	//has_arg有3个值,no_argument(或者是0),表示该参数后面不跟参数值
	// required_argument(或者是1),表示该参数后面一定要跟个参数值
	// optional_argument(或者是2),表示该参数后面可以跟,也可以不跟参数值
	int        *flag;    //用来决定,getopt_long()的返回值到底是什么。如果flag是null(通常情况),则函数会返回与该项option匹配的val值;如果flag不是NULL,则将val值赋予flag所指向的内存,并且返回值设置为0。
	int val; //和flag联合决定返回值
};

注意:
(1)数组的最后一个元素必须填充为0。
(2)has_arg取值为required_argument(或者是1)时,参数输入格式为:

--选项 值 或者 --参数=值

optional_argument(或者是2)时,参数输入格式只能为:

--选项=值。

(3)长选项名是可以使用缩写方式,比如:选项有–file,在不存在歧义的情况下,可以输入–f、–fi、–fil,均会被正确识别为–file选项。

举一个例子:

struct option long_options[] = 
{
	{"help",no_argument,NULL,'h'},
	{"file", required_argument,NULL,'f'},
	{"output",optional_argument,NULL,'o'}
	{0, 0, 0, 0}
}

如果命令行参数是--help,此时optarg是NULL,函数返回值’h’。

如果命令行的参数是--file 123.txt,那么调用getopt_long()将返回字符’f’,并且将字符串123.txt由optarg返回。这里需要注意,长格式选项参数的携带方式必须是–-option=param 或 --arg param,否则报错。

如果命令行参数是--output output.txt,选项参数的输入格式只能为--选项=值,不能是--选项 值,否则报错。此时,optarg是"output.txt",返回值’o’。

最后,当getopt_long()将命令行所有参数全部解析完成后,返回-1。

(3)longindex:如果longindex不是NULL,它指向getopt_long()获得的长选项在longopts的下标。

返回值:
(1)如果识别短选项,同getopt一样返回短选项字符;
(2)如果是识别长选项,由参数longopts中struct option.flag与struct option.val共同决定,具体参见上面参数的说明;
(3)选项参数解析完成后,返回-1;
(4)如果遇到存在歧义或未知的选项,则返回’?’。

注意: getopt_long()在识别短选项时,如果出现未知选项,可以使用全局变量optopt获取未知选项。但当识别长选项时出现未知选项,无法通过optopt获取未知的长选项,可以保存上一次optind,来获取非法命令选项。

具体示例:

int main(int argc, char * argv[])
{
	static struct option long_options[] = {
		{"help", no_argument, NULL, 'h'},
		{"file", required_argument, NULL, 'f'},
        {"output", optional_argument, NULL, 'o'},
        {0, 0, 0, 0}
	};
	static char* const short_options=(char *)"hf:o::";
	
	int option_index = 0;
	int ret=0;
    while((ret=getopt_long(argc,argv,short_options,long_options,&option_index))!=-1)
	{
		switch(ret)
		{
			case 'h':
				printf("HAVE option: -h\n");   
				break;
			case 'f':
				printf("HAVE option: -f\n"); 
				printf("The argument of -f is %s\n\n",optarg);
				break;
			case 'o':
				printf("HAVE option: -c\n");
				printf("The argument of -c is %s\n\n",optarg);
				break;
			case '?':
				break;
		}
	}
}

编译生成a.out,命令行输入如下内容:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out --file=123.txt
HAVE option: -f
The argument of -f is 123.txt

再看输入的例子:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out --fil 123.txt
HAVE option: -f
The argument of -f is 123.txt

当输入不完整的命令选项时,同样可以正确的解析,原因是getopt_long支持长选项的缩写。

输入错误的命令选项:

[dablelv@TENCENT64 ~/test/getopt]$ ./a.out --abc 123.txt
./a.out: unrecognized option '--abc'

4.getopt_long_only()

getopt_long_only() 的用法和上面的 getopt_long() 完全一样,唯一的区别在输入长选项的时候可以不用输入--而使用-

5.小结

历时近 5 小时,终于完成了此篇blog,效率有点低,争取下次提高效率,节省时间,做更多有意义的事情。由于个人水平有限,不足与错误在所难免,请不吝指教,万分感谢。


参考文献

[1] getopt(3) manual
[2] getopt.百度百科
[3] Linux下getopt()函数的简单使用
[4] getopt_long.百度百科
[5] getopt_long 函数

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值