1. getopt介绍

getopt函数主要用来解析命令行参数,getopt族的函数原型如下:

#include <unistd.h>

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

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

它们的用法类似,这里主要介绍getopt函数。

getopt函数共有三个参数

  • argc:命令行参数个数
  • argv:命令行参数存储数组
  • optstring:包含合法选项字符的字符串。(1)如果选项字符后面跟一个冒号,则表示该选项需要一个参数。(2)如果选项字符后面跟两个冒号,则表示该选项可以有一个可选的参数。(3)如果W后面跟一个分号,那么-W foo将和长选项--foo是同一个含义。注意:后两个特性为GUN扩展特性,在glibc 2之前的库中是不支持的。所以后面的介绍中不考虑这两种情况。

除了三个参数,getopt使用时,还需要用到四个全局变量

  • optind:用来保存下一个将要解析的参数在参数数组argv中的下标,初始值为1.最后,解析完的时候,这个参数的值应该等于参数个数argc。
  • optopt:用来指向正在被解析到的参数选项。
  • optarg:用来保存optopt对应选项的参数值。
  • opterr:当getopt在optstring中找不到输入选项时,就会向标准输错(stderr)打印一条错误信息,并且将该无法识别的选项存储在optopt中,并返回字符'?'。如果我们不想打印错误信息,可以将opterr设置为0.

解析过程:每当解析到一个选项optopt时,就会检查optopt是否出现在参数optstring中:(1)如果没有出现,则说明该选项非法,出错;(2)如果该选项在optstring中出现,且后面紧跟一个冒号(:),那么说明该选项必须跟一个参数,此时有两种情况:一种是参数直接跟在选项后面,另一种是参数和参数选项之间用空格隔开。对于第一种情况,只需要将optarg指向optopt后面的一个字符即可;对于第二种情况,则将optind加1,然后将argv[optind]赋值给optarg。(3)如果(1)和(2)的情况都没有发生,说明选项不带参数,这时候我们将optarg置为NULL,然后将optind加1.

一般我们都会重复调用(循环调用)getopt函数,来连续处理命令行选项。直到全部处理完或者出错。

getopt的返回值

  • 成功找到一个选项,则返回该选项字符(即optopt的值);
  • 所有的命令行参数处理完,则返回-1;
  • 如果选项在optstring中未找到,返回'?'
  • 如果一个选项需要参数,但却没有找到参数,则返回值取决于optstring中的第一个字符:如果是冒号(:),则返回冒号;如果不是冒号,返回问号(?)。

下面看一个例子:

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

int main(int argc, char *argv[])
{
    int opt;
    char name[20] = "Jim";
    char charact[100] = {0};
    int age = 0;

    while ((opt = getopt(argc, argv, "na:c::")) != -1)
    {
	switch (opt)
	{
	    case 'n':
		strcpy(name, "Allan");
		break;
	    case 'a':
		age = atoi(optarg);
		if (age <=0 || age >120)
		{
		    printf("invalid agen");
		    exit(EXIT_FAILURE);
		}
		break;
	    case 'c':
		if (optarg)
		    strncpy(charact, optarg, sizeof(optarg));
		else
		    printf("optarg is NULL.n");
		break;
	    default:  // ?
		printf("error option:%cn", optopt);
		exit(EXIT_FAILURE);
	}
    }

    printf("optind=%d.n", optind);

    if (age > 0)
	printf("%s is %d years old.n", name, age);
    if (strlen(charact))
	printf("%s is a %s guy.n", name, charact);

    exit(EXIT_SUCCESS);
}

编译运行结果

allan@ubuntu:temp$ ./a.out 
optind=1.
allan@ubuntu:temp$ ./a.out -n
optind=2.
allan@ubuntu:temp$ ./a.out -a 24
optind=3.
Jim is 24 years old.
allan@ubuntu:temp$ ./a.out -n -a 24
optind=4.
Allan is 24 years old.
allan@ubuntu:temp$ ./a.out -c
optarg is NULL.
optind=2.
allan@ubuntu:temp$ ./a.out -c good        # 注意点
optarg is NULL.
optind=2.
allan@ubuntu:temp$ ./a.out -cgood         # 注意点
optind=2.
Jim is a good guy.
allan@ubuntu:temp$ ./a.out -a 24 -n -cgood
optind=5.
Allan is 24 years old.
Allan is a good guy.

对于getopt的选项有以下几点注意事项

  • 不带参数的选项可以连写;
  • 各个参数不分先后顺序;
  • 对于必须带参数的选项,选项与参数之间必须要有至少一个空白字符(空格或tab键);
  • 对于可选参数的选项,如果加了参数,则参数与选项必须连在一起写,不能有空白,否则会出错。比如上面写了“注意点”的两行;
  • getopt只能处理短选项。所谓短选项是指一个"-"后面跟一个字母或数字。

如果想要处理长选项(两个"-",即"--"),需要使用我们下面要介绍的函数getopt_long。

2. getopt_long介绍

getopt只能处理短选项的命令行参数,如果想要处理长选项的命令行参数,需要使用getopt_long函数。其函数原型如下:

#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);

getopt_long既可以处理长选项,也可以处理短选项。如果程序只接受长选项,则将optstring设置为空字符串""(注意不是NULL)。长选项可以写为--arg=param 或者 --arg param。

getopt_long的第四个参数longopts为一个结构体指针,结构体如下:

struct option {
   const char *name;
   int         has_arg;
   int        *flag;
   int         val;
};

其成员变量含义说明如下:

  • name:长选项的名字;
  • has_arg:表示该选项是否需要参数,有三个选项(宏定义):no_argument(0)、required_argument(1)、optional_argument(2),分别代表无参、有参、可选三种情况;
  • flag:指定getopt_long如何返回结果:如果flag为NULL,getopt_long返回val;否则,返回0,此时如果选项找到了,flag指向一个变量,该变量被设置为val的值,如果选项没找到,保持不变。
  • val:getopt_long的返回值,或者flag指针指向的值。

使用getopt_long时,我们需要定义一个struct option数组,而longopts就指向这个结构体数组的第一个元素,且该数组的最后一个元素必须是全0。如果longindex值非NULL的话,它指向目前longopts在struct option数组中的索引。

getopt_long_only和getopt_long的区别在于,对于getopt_long_only来说,不论是"-"还是"--",它都优先认为是长选项,除非长选项没有匹配到,才将"-"按照短选项去匹配。

下面看一个例子(该例子来自Linux man 3 文档):

#include <stdio.h>     /* for printf */
#include <stdlib.h>    /* for exit */
#include <getopt.h>

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

   while (1) {
	   int this_option_optind = optind ? optind : 1;
	   int option_index = 0;
	   static struct option long_options[] = {
		   {"add",     required_argument, 0,  0 },
		   {"append",  no_argument,       0,  0 },
		   {"delete",  required_argument, 0,  0 },
		   {"verbose", no_argument,       0,  0 },
		   {"create",  required_argument, 0, 'c'},
		   {"file",    required_argument, 0,  0 },
		   {0,         0,                 0,  0 }
	   };

	   c = getopt_long(argc, argv, "abc:d:012",
				long_options, &option_index);
	   if (c == -1)
		   break;

	   switch (c) {
	   case 0:
		   printf("option %s", long_options[option_index].name);
		   if (optarg)
			   printf(" with arg %s", optarg);
		   printf("n");
		   break;

	   case '0':
	   case '1':
	   case '2':
		   if (digit_optind != 0 && digit_optind != this_option_optind)
			 printf("digits occur in two different argv-elements.n");
		   digit_optind = this_option_optind;
		   printf("option %cn", c);
		   break;

	   case 'a':
		   printf("option an");
		   break;

	   case 'b':
		   printf("option bn");
		   break;

	   case 'c':
		   printf("option c with value '%s'n", optarg);
		   break;

	   case 'd':
		   printf("option d with value '%s'n", 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);
}

要想我们的程序很灵活,我们往往会使用很多参数,而此时getopt和getopt_long对于我们就非常的重要了。