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:b: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

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 manual [2]getopt.百度百科 [3]Linux下getopt()函数的简单使用 [4]getopt_long.百度百科 [5]getopt_long 函数

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏偏前端工程师的驿站

Java魔法堂:类加载机制入了个门

一、前言                                 当在CMD/SHELL中输入 $ java Main<CR><LF> 后,Main程序...

2037
来自专栏Leetcode名企之路

jvm类加载机制

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。

1303
来自专栏乐百川的学习频道

设计模式(二十三) 模板模式

模板模式也是一种行为型模式,而且它非常好理解。当我们解决问题需要固定几个步骤, 这些步骤的顺序不能改变,而步骤的具体实现可以变化的时候,就可以使用模板模式。模板...

1686
来自专栏Python、Flask、Django

PHP htmlspecialchars_decode()函数

1274
来自专栏专注 Java 基础分享

虚拟机类加载机制

虚拟机把字节码文件从磁盘加载进内存的这个过程,我们可以粗糙的称之为「类加载」,因为「类加载」不仅仅是读取一段字节码文件那么简单,虚拟机还要进行必要的「验证」、「...

4667
来自专栏java系列博客

Java面试通关要点汇总集基础篇之参考答案

2234
来自专栏闻道于事

单例模式你会几种写法?

1195
来自专栏用户2442861的专栏

《Java虚拟机原理图解》 1.1、class文件基本组织结构

http://blog.csdn.net/luanlouis/article/details/39892027

912
来自专栏java相关

设计模式之—1.单例模式

单例模式实现方式有多种,其中比较简单的实现方式是直接定义一个private的属性,并直接初始化。相关实现如下:

1143
来自专栏DOTNET

【翻译】MongoDB指南/CRUD操作(一)

【原文地址】https://docs.mongodb.com/manual/ MongoDB CRUD操作(一) 主要内容:CRUD操作简介,插入文档,查询文档...

3479

扫码关注云+社区

领取腾讯云代金券