在学习开源代码过程中,经常遇到命令行解析函数 getopt,网上查阅了一些资料,总结一下。
说到命令行解析,最简单的方式就是利用C语言main函数的两个参数argc和argv来实现,当 C 运行时库程序启动代码调用 main() 时,会将命令行的参数传过来,参数个数放在argc中,参数内容放在argv中,命令行上除命令名之外的字符串。参数由多项构成,项与项之间用空白符彼此隔开。
//argc.c
#include <stdio.h>
int main(int argc,char *argv[])
{
int i;
for (i=0;i<argc;++i)
printf("%s ",argv[i]);
return 0;
}
输入
#gcc argc.c
#./a.out er ertrytuyu uiu -dfd gh
输出
#./a.out er ertrytuyu uiu -dfd gh
实际中程序的命令行参数比较复杂,用上述方法解析起来非常麻烦,为了简化对于命令行参数的解析,一些专家开发了getopt()函数,同时提供了几个外部变量,简化了命令行参数的解析。命令行参数可进一步分为选项和操作数,选项通常以'-'开头,选项后跟的就是操作数。例如 cat -n getopt.txt ,n是选项getopt.txt是操作数,还有以下约定:
a.选项名是单字符,且以短横‘-‘为前綴;
b.多个具有操作数的选项不能合并;其余的选项会被解析成第一个选项的操作数
c.多个不需要操作数的选项可以合并;如 ls -l -t -a = ls -lta
#include <unistd.h>
int getopt(int argc, char * const argv[], const char *optstring);
argc与argv跟main函数中argc和argv意义相同
optstring是一个包含合法选项字符的字符串,字符后接一个冒号:表示该选项后必须跟一个参数。参数紧跟在选项后或者以空格隔开。单个字符后跟两个冒号,表示该选项后可以跟一个参数,不能用空格隔开,中间有空格相当于不带参数。
extern char *optarg; //指向选项的操作数
extern int optind, //下一个选项的索引(在argv中的索引)
extern int opterr, //当opterr=0时,getopt不向stderr输出错误信息。
extern int optopt; //当命令行选项字符不包括在optstring中或者选项缺少必要的参数时,该选项存储在optopt中,同时getopt返回'?'
下面详细讲下getopt()解析命令行参数的基本过程:以optstring="ab:c:de::" 为例
0 1 2 3 4 5 6 7 8 9
$ ./a.out f1 -a -b -c f2 -d f3 -e f4
由optstring可知:选项a,d没有参数,b,c有一个参数,选项e可以有一个参数且必须紧跟在选项后不能以空格隔开。 扫描过程中,optind是下一个选项的索引, 非选项参数将跳过,同时optind增1。optind初始值为1。当扫描argv[1]时,为非选项参数,跳过,optind=2;扫描到-a选项时, 下一个将要扫描的选项是-b,则optind更改为3;扫描到-b选项时,因为b必须有操作数,则会将-c解析成选项b的参数),optind=5,扫描到f2为非选项跳过,optind=6;扫描到-d选项,后面没有参数,optind=7;扫描到f3为非选项跳过,optind=8;扫描到-e后面本来应该有参数,但是有空格认为e的参数为空。optind=9,解析完成后,getopt会将argv数组修改成下面的形式:
0 1 2 3 4 5 6 7 8 9
$ ./a.out -a -b -c -d -e f1 f2 f3 f4
解析完成后,optind会指向非选项的第一个参数,如上面,optind将会指向f1。
下面关门放代码:
//getopt1.c
#include <stdio.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int ch;
int i;
while((ch=getopt(argc,argv,"a:b::cde"))!=-1)
{
printf("optind:%d\n",optind);
printf("optarg:%s\n",optarg);
printf("ch:%c\n",ch);
printf("optopt+%c\n",optopt);
}
printf("%d\n",optind);
for (i=0;i<argc;++i)
printf("%s ",argv[i]);
}
输入
#gcc getopt1.c
#./a.out f1 f2 f3 -a123 -b456 -cde
输出
optind:5 //跳过非选项参数 f1 f2 f3 解析-a123 指向下一个要解析的-b456
optarg:123 //本次选项的操作数
ch:a //本次选项
optopt+ //因为解析没出错,所以optopt为空
optind:6
optarg:456
ch:b
optopt+
optind:6
optarg:(null)
ch:c
optopt+
optind:6
optarg:(null)
ch:d
optopt+
optind:7 //虽然这里仍然会加1,尽管索引7已经超过argv的范围
optarg:(null)
ch:e
optopt+
4
./a.out -a123 -b456 -cde f1 f2 f3 //这是解析过后的argv,选项和操作数在左,其余参数在右
上面是解析过程没出错时的情况,下面介绍异常情况
a.无效的选项
输入
#./a.out -c1
输出
optind:1 //getopt认为1是下一个要解析的选项 所以 optind=1
optarg:(null)
ch:c
optopt+ //以上为-c的解析
./a.out: invalid option -- '1' //这里解析出错 1不是合法的选项,是在执行getopt函数时报错
optind:2
optarg:(null)
ch:? //出错getopt返回 ?
optopt+1 //optopt存储出错选项1
2 //这是解析完后的optind的值
./a.out -c1 //这是解析过后的argv
b.第二种出错情况,没有选项传参
输入
#./a.out -a
输出
./a.out: option requires an argument -- 'a'
optind:2
optarg:(null)
ch:?
optopt+a
2
./a.out -a
补充:
在调用getopt()之前,将opterr设置为0,这样可以阻止getopt()函数输出错误消息。如果optstring参数的第一个字符是冒号,那么getopt()函数就会根据错误情况返回不同字符,“无效选项”getopt()会返回'?',并且optopt包含了无效选项字符(这是正常的行为)。“缺少选项操作数”getopt()会返回':',如果optstring的第一个字符不是冒号,那么只要出错getopt()返回'?'。
总结:这两天被这个getopt搞得晕头转向,主要还是不了解它解析命令行参数的过程,除了getopt,getopt_long,getopt_long_only函数也可以解析命令行参数,有时间再研究。