函数 —— 分析命令行参数 getopt() getopt_long() getopt_long_only()

为什么需要命令行解析函数?
只按顺序处理参数的话,一些“可选参数”的功能将很难实现。
在Linux中,我们可以使用getopt、getopt_long、getopt_long_only来对这个问题进行处理
为什么是三个函数?
因为这三个函数的分工不同:
getopt()是获取短参数的:
        可以获取-a,-l类型的短参数,也可以-name合并的获取到-n -a -m -e到optstring中匹配;
getopt_long()是获取长参数的:
        还可以获取--name这种参数,当-name时,会拆成-n -a -m -e到optstring中进行匹配;
getopt_long_only()是获取长参数的,与getopt_long()使用相同的参数表:
        可以获取--name或者-name两种选项都当做长参数来匹配;
        当遇到-name长参数时,不能在longopts中匹配时才将其拆分成-n -a -m -e到optsing中进行匹配。

getopt()函数

1、功能描述;

     getopt - parse command options (enhanced)  (解析命令选型(增强))

2、表头文件:

       #include<unistd.h>

3、定义函数:

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

         extern char *optarg;
         extern int optind, opterr, optopt;

4、optstring中的内容的意义:

        字符串参数optstring -- optstring是由选项Option字母组成的字符串

        例如:getopt(argc,argv,"ab:c:de::")

        1、单个字符,[a] [b] [c] [d] [e], 表示选项;

        2、单个字符后接一个冒号 ':' , [b:]  [c:] ,表示该选项后必须跟一个参数。

                                参数紧跟在选项后或或者以空格隔开,参数的指针赋值给optarg;

        3、单个字符后有两个冒号 '::' ,[ e:: ],表示该选项后可以跟一个参数,也可以不跟。

                            如果跟一个参数,参数必须紧跟在选项后不能以空格隔开,参数指针赋值给optarg(未赋值 optarg = NULL);

5、getopt设置的全局变量:

        optarg——指向当先选项的参数(如果有)的指针;(当匹配一个选项后,如果该选项带选项参数,则optarg指向选项参数字符串;若该选项不带选项参数,则optarg为NULL;若该选项的选项参数为可选时,optarg为NULL表明无选项参数,optarg不为NULL时则指向选项参数字符串。)

        optind——再次调用getopt()时的下一个argv指针的索引;(一个待处理元素在argv中的索引值。即下一次调用getopt的时候,从optind存储的位置处开始扫描选项。当getopt()返回-1后,optind是argv中第一个Operands的索引值。optind的初始值为1。

        opteer——opterr的值非0时,在getopt()遇到无法识别的选项,或者某个选项丢失选项参数的时候,getopt()会打印错误信息到标准错误输出。opterr值为0时,则不打印错误信息。opterr默认为1。

        optopt——最后一个未知选项。(在上述两种错误之一发生时,一般情况下getopt()会返回'?',并且将optopt赋值为发生错误的选项。 

来看个实例,我们定义字符串参数为const char *optstring = "a:b:c::d";
从前面的知识点,我们很容易得出结论:
        选项a,b是必须要加一个参数的;        
        选项c的选项参数是可选的,即如果提供选项c的选项参数的话,那么选项参数必须紧跟选项c之后,不能以空格隔开
        选项d是不需要选项参数的  选项b,c需要选项参数,而选项e的选项参数是可选的,

深入理解   optarg   全局变量: 

#include<stdio.h>
#include<unistd.h>
int main(int argc ,char **argv)
{
        int opt;
        const char *optstring = "a:b:c::d";
        while((opt = getopt(argc, argv, optstring)) != -1)
        {
                printf("opt = %c\n",opt);
                printf("optarg = %s\n",optarg);
        }
        return 0;
}

执行结果分析如下:

//选项必须匹配参数时,但未匹配参数时候,getopt返回?字符, optarg为NULL
[root@localhost getopt]# ./a.out -a
./a.out: option requires an argument -- 'a'
opt = ?
optarg = (null)
//选项必须匹配参数时,且匹配参数后,getopt返回选项参数字符,optarg指向选项参数字符串
[root@localhost getopt]# ./a.out -a aa
opt = a
optarg = aa
//选项匹配参数可选时,但未匹配参数时候,getopt返回选项参数字符,optarg为NULL
[root@localhost getopt]# ./a.out -c
opt = c
optarg = (null)
//选项匹配参数可选时,匹配好参数但是未紧跟在选项后,getopt返回该选项字符,optarg为NULL
[root@localhost getopt]# ./a.out -c cc
opt = c
optarg = (null)
//选项匹配参数可选时,匹配参数 并且紧跟在选项参数后,getopt返回该选项字符,optarg指向选项参数字符串
[root@localhost getopt]# ./a.out -ccc
opt = c
optarg = cc
//选项不需要匹配参数时,getopt返回该选项字符,optarg为NULL
[root@localhost getopt]# ./a.out -d
opt = d
optarg = (null)
//选项未定义时候,getopt返回?字符,optarg为NULL
[root@localhost getopt]# ./a.out -A
./a.out: invalid option -- 'A'
opt = ?
optarg = (null)

深入理解   optind   全局变量:

int main(int argc ,char **argv)
{
        int opt;
        const char *optstring = "a:b:c::d";
        while((opt = getopt(argc, argv, optstring)) != -1)
        {
                printf("opt = %c\n",opt);
                printf("optind = %d\n",optind);
                printf("argv[optind - 1] = %s\n",argv[optind - 1]);
        }
        return 0;
}
执行结果分析如下:
//表示下一次调用getopt时候,下一个argv值的索引;即下一次调用getopt时,从optind存储的位置处开始扫描
[root@localhost getopt]# ./a.out -a aa -b bb -c cc -d
opt = a
optind = 3
argv[optind - 1] = aa
opt = b
optind = 5
argv[optind - 1] = bb
opt = c
optind = 6
argv[optind - 1] = -c
opt = d
optind = 8
argv[optind - 1] = -d
//并没有先扫描AA getopt()返回-1后,optind是argv中第一个operands的索引值。optind的初始值为1.
[root@localhost getopt]# ./a.out -a aa AA -b bb -cCC -d
opt = a
optind = 3
argv[optind - 1] = aa
opt = b
optind = 6
argv[optind - 1] = bb
opt = c
optind = 7
argv[optind - 1] = -cCC
opt = d
optind = 8
argv[optind - 1] = -d
深入理解   opterr   全局变量:
int main(int argc ,char **argv)
{
        int opt;
        printf("opterr = %d\n",opterr);
        const char *optstring = "a:b:c::d";
        while((opt = getopt(argc, argv, optstring)) != -1){}
        return 0;
}
执行结果分析如下:
//opterr默认值为1 当getopt遇到无法识别的选项  或者某个选项丢失选项参数的时候,getopt会打印错信息到标准错误输出
[root@localhost getopt]# ./a.out -A
opterr = 1
./a.out: invalid option -- 'A'
[root@localhost getopt]# ./a.out -a
opterr = 1
./a.out: option requires an argument -- 'a'

但是当设置opterr为0时,则不会打印这些信息(在getopt函数之前加入代码  opterr = 0; 即可)。

深入理解   optopt   全局变量:

int main(int argc ,char **argv)
{
        int opt;
        const char *optstring = "a:b:c::d";
        opterr = 0;
        while((opt = getopt(argc, argv, optstring)) != -1)
        {
                printf("opt = %c\n",opt);
                printf("optopt = %c\n",optopt);
        }
        return 0;
}
执行结果分析如下:
//在getopt()遇到无法识别的选项 或者某个选项  丢失选项参数 的时候
//两种错误之一发生时,一般情况下getopt会返回‘?’ 并且optopt赋值为发生错误的选项
//由结果知道  并不会判断  某个选项参数值可选时  有无输入  参数  可以用optarg判断
[root@localhost getopt]# ./a.out -A
opt = ?
optopt = A
[root@localhost getopt]# ./a.out -a
opt = ?
optopt = a
[root@localhost getopt]# ./a.out -c
opt = c
optopt = 

综合描述:

int main(int argc ,char **argv)
{
        int opt;
        const char *optstring = "a:b:c::d";
        while((opt = getopt(argc, argv, optstring)) != -1)
        {
                printf("opt = %c\n",opt);
                printf("optarg = %s\n",optarg);
                printf("optind = %d\n",optind);
                printf("argv[optind - 1] = %s\n",argv[optind - 1]);
                printf("optopt = %c\n\n",optopt);
        }
        return 0;
}

执行结果分析如下:

[root@localhost getopt]# ./a.out -A -d -cCC -a aa -b
./a.out: invalid option -- 'A'
opt = ?
optarg = (null)
optind = 2
argv[optind - 1] = -A
optopt = A

opt = d
optarg = (null)
optind = 3
argv[optind - 1] = -d
optopt = A

opt = c
optarg = CC
optind = 4
argv[optind - 1] = -cCC
optopt = A

opt = a
optarg = aa
optind = 6
argv[optind - 1] = aa
optopt = A

./a.out: option requires an argument -- 'b'
opt = ?
optarg = (null)
optind = 7
argv[optind - 1] = -b
optopt = b

6、getopt()使用时候,经常会犯的错误:        

无法识别的选项(Invalid option) 和丢失选项参数(Missing option argument)
通常情况下,getopt()在发现这两个错误时,会打印相应的错误信息,并且返回字符"?" (示例见上方 深入理解   optarg   全局变量)。例如,遇见无法识别的选项时会打印"invalid option"(深入理解   optarg   全局变量   -》 # ./a.out -A),发现丢失参数时打印"option requires an argument"深入理解   optarg   全局变量   -》 # ./a.out -a。但是当设置opterr为0时,则不会打印这些信息,因此为了便于发现错误,默认情况下,opterr都是非零值(示例见上方深入理解   optopt   全局变量)。

如果你想亲自处理这两种错误的话,应该怎么做呢? 首先你要知道什么时候发生的错误是无法识别的选项,什么时候发生的错误是丢失选项参数。如果像上面描述的那样,都是返回字符"?"的话,肯定是无法分辨出的。有什么办法吗? 有! getopt()允许我们设置optstring的首字符为冒号":",在这种情况下,当发生无法识别的选项错误时getopt()返回字符"?",当发生丢失选项参数错误时返回字符":"。这样我们就可以很轻松地分辨出错误类型了,不过代价是getopt()不会再打印错误信息了,一切事物都由我们自己来处理了。

分辨错误类型无法识别的选项、丢失选项参数, 并打印错误信息:

int main(int argc ,char **argv)
{
        int opt;
        const char *optstring = ":a:b:c::d";  //注意在字符串参数最前面有一个冒号
        while((opt = getopt(argc, argv, optstring)) != -1)
        {
                printf("opt = %c\n",opt);   //可以根据opt返回的字符的类型 判断是什么错误类型
                printf("optarg = %s\n",optarg);
                printf("optopt = %c\n",optopt);   //发生错误的选项 赋值给 optopt
                if(ch == 63) //?  无法识别的选项的判断
                {
                        printf("./a.out: invalid option -- '%c'\n\n",optopt);
                }
                if(ch == 58) //:  丢失选项参数的判断
                {
                        printf("./a.out: option requires an argument -- '%c'\n",optopt);
                }
        }
        return 0;
}

结果分析:

[root@localhost getopt]# ./a.out -a -A
opt = a
optarg = -A
optopt = 
[root@localhost getopt]# ./a.out -A -a
opt = ?
optarg = (null)
optopt = A
./a.out: invalid option -- 'A'

opt = :
optarg = (null)
optopt = a
./a.out: option requires an argument -- 'a'

7、getopt()参数是如何操作命令行参数的(如何扫描参数的):


command name: 是程序的名字;
option: 操作的选项;
option argument:选型 option所需要的信息:
operands: 操作对象 又称为 nonoption argument。
getopt()的默认模式扫描模式是这样的: getopt()从左到右按顺序扫描argv[1]到argv[argc-1],并将选项Option和选项参数Option argument按它们在命令行上出现的次序放到argv数组的左边,而那些Operands则按它们出现的次序放在argv的后边。也就是说,getopt()在默认扫描模式下,会重新排序argv数组。
来看个实例,假设我们调用getopt(argc, argv, "ab:c:de::");
从前面的知识点,我们很容易得出结论:选项a,d是不需要选项参数的,选项b,c需要选项参数,而选项e的选项参数是可选的,即如果提供选项e的选项参数的话,那么选项参数必须紧跟选项e之后,不能以空格隔开
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
static int cnt = 1;
static void print(int optc,int argc,char *argv[],int optind)
{
        int i;
        printf("%02d: optc - '%c',argv:",cnt++,optc);
        for(i=0;i<argc;i++)
        {
                printf("%s ",argv[i]);
        }
        printf("----- optind = %d\n",optind);
}
int main(int argc ,char *argv[])
{
        int optc;
        print('0',argc,argv,optind);
        //while ((optc = getopt(argc, argv, ":ab:c:de::")) != -1) {
        //while ((optc = getopt(argc, argv, "+ab:c:de::")) != -1) {
        //while ((optc = getopt(argc, argv, "-ab:c:de::")) != -1) {
        while ((optc = getopt(argc, argv, "-:ab:c:de::")) != -1) {  //既可以不排序 也可以判断是那种错误形式
                print(optc, argc, argv, optind);
                switch (optc) {
                        default:
                                break;
                }
        }
        print('0',argc,argv,optind);
        return 0;
}
运行情况如下:

当然在真实情况下,一个程序很少需要这么多的Operands,这里只是为了更清楚地演示getopt()是如何扫描命令行参数的。
扫描过程中,要时刻铭记 optind是下一个待处理元素在argv中的索引,当遇到Operands的时候则跳过,optind数值增加跳过的Operands个数 。好,现在我们根据这些规则,详细分析下刚刚程序的扫描过程:
第一行 :即getopt()扫描重排序之前,可以看到optind的值默认被初始化为1。
第二行 :getopt()首先从operand1开始扫描,发现operand1是Operands,则跳过,optind增加1等于2指向-a,继续扫描。扫描到-a时发现是有效选项,则optind增加1等于3指向operand2,然后返回选项a。
第三行 :在继续扫描前,getopt()重新排序argv数组,将“-a”和“operand1”的位置互换。继续扫描,发现operand2是Operands,跳过,optind增加1等于4指向-b,继续扫描。发现-b是有效选项,因为选项b需要参数,因此把barg的首地址赋给optarg,optind增加2等于6指向operand3,返回选项b。
第四行 :在继续扫描前,getopt()重新排序argv数组,将“-b barg”和“operand1 operand2”的位置互换。继续扫描,发现operand3是Operands,跳过,optind增加1等于7指向-c,继续扫描。扫描到-c是发现是有效的选项,因为选项c跟选项b一样,都需要参数,因此处理过程是一样的,把carg的首地址赋给optarg,optind增加2等于9指向operand4,返回选项c。
第五行 :在继续扫描前,getopt()重新排序argv数组,将“-c carg”和“operand1 operand2 operand3”的位置互换。继续扫描,发现operand4是Operands,跳过,optind增加1等于10指向-d,继续扫描。扫描到-d时发现是有效选项,因为选项d不需要参数,因此直接optind增加1等于11指向operand5,返回选项d。
第六行 :在继续扫描前,getopt()重新排序argv数组,将“-d”和“operand1 operand2 operand3 operand4”的位置互换。继续扫描,发现operand5是Operands,跳过,optind增加1等于12指向operand6,继续扫描。扫描到operand6时发现依然是Operands,跳过,optind增加1等于13指向-e,继续扫描。扫描到-e时发现是有效选项,因为后面的operand7与-e之间有间隔,因此这里选项e没有参数。optind增加1等于14指向operand7,返回选项e。
第七行 :在继续扫描前,getopt()重新排序argv数组,将“-e”和“operand1 operand2 operand3 operand4 operand5 operand6”的位置互换。继续扫描,发现operand7是Operands,跳过,optind增加1等于15指向argv[argc],即NULL。 至此扫描完毕,getopt()重新设置optind为8,是其指向第一个Operands,即operand1,最后返回-1停止扫描。

--------------------------------------------------------------------------------------------------------------------------------------------

但是有的时候,你可能希望你的程序接收的参数中Operands必须放在最后,也就是遇到第一个Operands就立即停止参数扫描

例如:遇到operand1时候就停止扫描

可以通过将optstring的首字符设置为‘+’来改变getopt()的扫描模式。我们可以修改前面的程序为getopt(argc, argv, "+ab:c:de::");


可以看到,这种扫描模式下,的确遇到第一个Operands时就立即停止扫描,忽略后续所有命令行参数,也不会进行重排序操作。

--------------------------------------------------------------------------------------------------------------------------------------------

此外,还有最后一种扫描模式是通过将optstring首字符设置为“-”来实现的。我们将程序改为getopt(argc, argv, "-ab:c:de::");先来看下运行结果:

可以看到在这种模式下,getopt()会按顺序扫描整个命令行参数,遇到有效选项时会正常处理,而遇到Operands时却是这样处理的:返回1,optarg赋值为Operands的首地址。

b, c字符后只识别一个,Operands;a,d,e后跟几个识别几个;

至此,我们就介绍完了getopt()的所有扫描模式。需要补充的两点是:
1. 在后两种扫描模式的情况下,如果还需要自己来区分无效选项丢失选项参数这两种错误的话,那么应该将optstring的第二个字符设置为字符‘:’。
2. 不管在哪种扫描模式下,当遇到字符串“--”时都会停止扫描,后续的命令行参数当作Operands来处理。所以如何删除文件名以‘-’起始的文件?例如当前目录下有个文件的名字是“-foo”,如何删除呢? 有两种办法:1. rm ./-foo 2. rm -- -foo

--------------------------------------------------------------------------------------------------------------------------------------------


getopt_long()函数

1、功能描述;

    getopt, getopt_long, getopt_long_only, optarg, optind, opterr, optopt - Parse command-line options

2、表头文件:

      #include <getopt.h>

3、定义函数:

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

4、函数中的内容的意义:

        字符串参数optstring -- optstring是由选项Option字母组成的字符串

负责处理短参数。也称为选项指定符字符串,该字符串告诉getopt哪些选项可用,以及它们是否有关联值。optstring只是一个字符列表。

        例如:getopt(argc,argv,"ab:c:de::")

        1、单个字符,[a] [b] [c] [d] [e], 表示选项;

        2、单个字符后接一个冒号 ':' , [b:]  [c:] ,表示该选项后必须跟一个参数。

                                参数紧跟在选项后或或者以空格隔开,参数的指针赋值给optarg;

        3、单个字符后有两个冒号 '::' ,[ e:: ],表示该选项后可以跟一个参数,也可以不跟。

                            如果跟一个参数,参数必须紧跟在选项后不能以空格隔开,参数指针赋值给optarg(未赋值 optarg = NULL);

       参数longopts,其实是一个结构的示例:

负责处理长参数。指向一个由option结构体组成的数组,那个数组的每一个元素都指明了一个长参数(形如”–name”的参数)名称和性质

         struct option {
               const char *name; //name 表示是长参数名
               int         has_arg;   //has_arg 有三个值 
               int        *flag;//用来决定,getopt_long()的返回值到底是什么。
                                   //如果flag是null(通常情况),则函数会返回与该项option匹配的val值;
                                  //如果flag不是null,则将val值写入flag所指向的内存,并且返回值设置为0;
               int         val;//和flag 联合决定返回值

           };

        参数flag,表示当前长参数在longopts中的索引值。
       //has_arg有3个值,
                           //no_argument(或者是0),表示该参数后面不跟参数值

                     // required_argument(或者是1),表示该参数后面一定要跟个参数值。参数输入格式为:--参数 值 或者 --参数=值。
                             // optional_argument(或者是2),表示该参数后面可以跟,也可以不跟参数值。参数输入格式只能为:--参数=值。
参数longindex:
如果longindex非空,它指向的变量将记录当前找到参数符合longopts里的第几个元素的描述,即是longopts的下标值。

5、示例:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
 
int
main(int argc, char **argv)
{
   int opt;
   int digit_optind = 0;
   int option_index = 0;
   char *optstring = "a:b:c:d";
   static struct option long_options[] = {
       {"reqarg", required_argument, NULL, 'r'},
       {"noarg",  no_argument,       NULL, 'n'},
       {"optarg", optional_argument, NULL, 'o'},
       {0, 0, 0, 0}
   };
 
   while ( (opt = getopt_long(argc, argv, optstring, long_options, &option_index)) != -1)
   {
        printf("opt = %c\n", opt);
        printf("optarg = %s\n", optarg);
        printf("optind = %d\n", optind);
        printf("argv[optind - 1] = %s\n",  argv[optind - 1]);
        printf("option_index = %d\n\n", option_index);
   }
 
   return 0;
}

执行结果分析:

[root@localhost getopt]# ./a.out --optarg=sd --reqarg=asd  --noarg 
opt = o
optarg = sd
optind = 2
argv[optind - 1] = --optarg=sd
option_index = 2

opt = r
optarg = asd
optind = 3
argv[optind - 1] = --reqarg=asd
option_index = 0

opt = n
optarg = (null)
optind = 4
argv[optind - 1] = --noarg
option_index = 1
最后说说getopt_long_only函数,它与getopt_long函数使用相同的参数表,在功能上基本一致,只是getopt_long只将--name当作长参数,但getopt_long_only会将--name和-name两种选项都当作长参数来匹配。在getopt_long在遇到-name时,会拆解成-n -a -m -e到optstring中进行匹配,而getopt_long_only只在-name不能在longopts中匹配时才将其拆解成-n -a -m -e这样的参数到optstring中进行匹配。

参考链接:https://blog.csdn.net/cashey1991/article/details/7942809

                 https://blog.csdn.net/astrotycoon/article/details/46047449

猜你喜欢

转载自blog.csdn.net/weixin_42167759/article/details/80922026