在进行Linux程序开发时,我们最常碰到的一个问题就是:如何设计命令行参数以及如何完成对命令行参数的解析呢?在本文,我们将对这个问题进行详细的阐述和案例分析。
一、库函数详解
当进行命令行参数解析时,主要涉及的库函数包括getopt,getopt_long以及getopt_long_only。其中,主要的参数包括optarg,optind,opterr以及optopt。
现举例说明:
$ myprog -a vv --add -b --file a.txt - -- -e c.txt
Linux中的命令行选项有两种类型,包括短选项和长选项。其中,短选项以'-'作为前导符,长选项以'--'作为前导符。
对于以上命令,可以解释如下:
1. a是短选项,带一个参数vv;
2. add是长选项,无参数;
3. b是短选项,无参数;
4. file是长选项,带一个参数a.txt;
5. -是参数,通常表示标准输入,stdin;
6. --是一个指示符,表明停止扫描参数,其后所有部分都是参数,而不是选项;
7. -e是参数;
8. c.txt是参数。
当执行命令行参数解析时,可以使用一些用于分析命令行参数的函数,它们的原型如下:
#include <unistd.h>
int getopt(int argc, char* const argv[], const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
#define _GNU_SOURCE
#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函数,其原型如下:
int getopt(int argc, char* const argv[], const char *optstring);
其中,argc表示命令行参数个数,argv表示命令行参数数组,optstring则表示如何分析命令行参数。
关于optstring,有如下几点说明:
- 如果选项带参数,该选项后接冒号,比如"a:b",则表示a带参数,而b不带参数;
- 如果选项带可选参数,该选项后接两个冒号,比如"a::b",则表示a可能有参数,也可能不带参数;
- 如果optstring的起始字符为':',则表示如果指明选项带参数,而实际命令行没有参数时,getopt返回':'而不是'?'(默认情况下返回'?',和无法识别的参数返回一样);
- 如果optstring的起始字符为'+',则表示一旦遇到一个无选项参数,马上停止扫描,随后的部分当作参数来解释;
- 如果optstring的起始字符为'-',则表示如果遇到无选项参数,则把它当作选项1(不是字符'1')的参数。
- 该函数每解析完一个选项,就返回该选项字符。
- 如果选项带参数,则参数保存在optarg中。如果选项带可选参数,而实际无参数时,optarg为NULL。
- 当遇到一个不在optstring指明的选项时,返回字符'?'。如果在optstring指明某选项带参数而实际没有参数时,返回字符'?'或者字符':',则根据optstring的起始字符而定。这两种情况选项的实际值被保存在optopt中。
- 当解析错误时,如果opterr为1则自动打印一条错误消息(默认),否则不打印。
- 当解析完成时,函数返回-1。
- 每当解析完一个argv,optind(选项索引)就会递增。如果遇到无选项参数,getopt默认会把该参数调后一位,再解析下一个参数。如果解析完成后还有无选项的参数,则optind指示的是第一个无选项参数在argv中的索引。
对于可以同时分析长选项和短选项的getopt_long函数,其原型如下:
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
相较于getopt函数,它同时能够接收长选项。在接收长选项之前,必须定义一个option结构体数组longopts,用于存储希望解析的长选项信息。
对于option结构体,其定义如下:
struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
对于结构体的每个成员,含义如下:
- name表示长选项的名称。
- has_arg表示该选项是否带参数,包括no_argument,required_argumen以及optional_argument。
- flag表示长选项如何返回,如果flag为NULL,则getopt_long返回val。否则返回0,flag指向一个值为val的变量。如果该长选项没有发现,flag保持不变。
- val表示长选项的返回值,或需要被加载到flag所指向的变量中。
另外,对于longopts数组,最后一个元素必须被全部填充为0,即{0, 0, 0, 0}。同时,getopt_long函数的最后一个参数longindex在函数返回时指向被搜索到的选项在longopts数组中的下标。longindex可以为NULL,表示不需要返回该值。
getopt_long_only类似于getopt_long,但是它把'-'开头的选项当作长选项来处理。如果该选项与长选项不匹配,而与短选项匹配,则可以作为短选项解析。在短选项找到的时候,getopt_long和getopt_long_only的表现和getopt一样。如果长选项找到了,如果flag为 NULL,返回val,否则返回0。错误情况的处理和getopt一样,只是返回'?'时还可能是别的情况引起的:选项含糊不明确或者无关参数。
二、举例说明
代码如下:
#include <stdio.h>
#include <stdlib.h>
#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, "a:bc:d:012",
long_options, &option_index);
if (c == -1) {
printf("arguments parse work completed.\n");
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-element.\n");
}
digit_optind = this_option_optind;
printf("option %c\n", c);
break;
case 'a':
printf("option a with value '%s'\n", optarg);
break;
case 'b':
printf("option b\n");
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);
}
测试如下:
zjl@ubuntu:~/workset/cmdparse$ ./myprog -a vv --add -b --file a.txt --delete d
option a with value 'vv'
option add with arg -b
option file with arg a.txt
option delete with arg d
arguments parse work completed.