这个函数有点像无限迷宫,正确的路和错误的路都有很多,我们只需要能够满足当前需求就可以了,完全没有必要去探索每一条路。虽然,我很久以前试图这样干过。过滤后的回忆,只剩感觉了,过滤的多了,感觉都被冲散了。
getopt函数
Linux提供了getopt函数,它支持需要关联值和不需要关联值的选项,而且简单易用。
#include <unistd.h>
int getopt(int argc, char * const argv[],const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
getopt函数将传递给程序的main函数的argc和argv作为参数,同时接受一个选项指定符字符串optstring,该字符串告诉getopt哪些选项可用,以及它们是否有关联值。optstring只是一个字符列表,每个字符代表一个单字符选项。如果一个字符后面紧跟一个冒号(:),则表明该选项有一个关联值作为下一个参数。bash中的getopts命令执行类似的功能。
例如,我们可以用下面的调用来处理上面的例子:
getopt(argc,argv,"if:lr");
它允许几个简单的选项:-i、-l、-r和-f,其中-f选项后要紧跟一个文件名参数。使用相同的参数,但以不同的顺序来调用命令将改变程序的行为。你可以在本章的下一个实验部分进行尝试。
getopt的返回值是argv数组中的下一个选项字符(如果有的话)。循环调用getopt就可以依次得到每个选项。getopt有如下行为。
❑ 如果选项有一个关联值,则外部变量optarg指向这个值。
❑ 如果选项处理完毕,getopt返回-1,特殊参数--将使getopt停止扫描选项。
❑ 如果遇到一个无法识别的选项,getopt返回一个问号(? ),并把它保存到外部变量optopt中。
❑ 如果一个选项要求有一个关联值(例如例子中的-f),但用户并未提供这个值,getopt通常将返回一个问号(? )。如果我们将选项字符串的第一个字符设置为冒号(:),那么getopt将在用户未提供值的情况下返回冒号(:)而不是问号(? )。
外部变量optind被设置为下一个待处理参数的索引。getopt利用它来记录自己的进度。程序很少需要对这个变量进行设置。当所有选项参数都处理完毕后,optind将指向argv数组尾部可以找到其余参数的位置。
有些版本的getopt会在第一个非选项参数处停下来,返回-1并设置optind的值。而其他一些版本,如Linux提供的版本,能够处理出现在程序参数中任意位置的选项。注意,在这种情况下,getopt实际上重写了argv数组,把所有非选项参数都集中在一起,从argv[optind]位置开始。对GNU版本的getopt而言,这一行为是由环境变量POSIXLY_CORRECT控制的,如果它被设置,getopt就会在第一个非选项参数处停下来。此外,还有些getopt版本会在遇到未知选项时打印出错信息。注意,根据POSIX规范的规定,如果opterr变量是非零值,getopt就会向stderr打印一条出错信息。
实验getopt函数
在这个实验中,你将在程序中使用getopt函数,并将新程序命名为argopt.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
int main(int argc,char **argv)
{
int opt;
while((opt = getopt(argc,argv,"if:lr")) != -1){
switch (opt)
{
case 'i':
case 'l':
case 'r':
DEBUG_INFO("option:%c",opt);
break;
case 'f':
DEBUG_INFO("option:%c filename = %s",opt,optarg);
break;
case ':':
DEBUG_INFO("option:%c needs a value",opt);
break;
case '?':
DEBUG_INFO("unkown option:%c needs a value",optopt);
break;
default:
break;
}
}
DEBUG_INFO("optind = %d ",optind);
for(;optind < argc;optind++){
DEBUG_INFO("argument:%s",argv[optind]);
}
return 0;
}
测试结果:
实验解析
这个程序循环调用getopt对选项参数进行处理,直到处理完毕,此时getopt返回-1。每个选项(包括未知选项和缺少关联值的选项)都有相应的处理动作。根据使用的getopt版本,你看到的输出可能和上面显示的略有不同,尤其是出错信息部分,但含义都是明确的。当所有选项都处理完毕后,程序像以前一样把其余参数都打印出来,但这次是从optind位置开始。
大部分情况下,我们不必考虑这么复杂的问题的。如下例所示:
通过参数配置IP地址和端口号
想要达到的效果如下所示:
argopt -i 192.168.0.5 -p 1502
-i选项:参数是IP地址
-p选项:参数是端口号
测试代码如下所示
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
#define DEFAULT_IP "0.0.0.0"
#define DEFAULT_PORT ((uint16_t)502)
char ip[20];
uint16_t port;
int main(int argc,char **argv)
{
int opt;
while((opt = getopt(argc,argv,"i:p:")) != -1){
switch (opt)
{
case 'i':
DEBUG_INFO("option:%c ip = %s",opt,optarg);
break;
case 'p':
DEBUG_INFO("option:%c port = %s",opt,optarg);
break;
case '?':
DEBUG_INFO("unkown option:%c needs a value",optopt);
break;
default:
DEBUG_INFO("unkown option:%c needs a value",optopt);
break;
}
}
DEBUG_INFO("optind = %d ",optind);
for(;optind < argc;optind++){
DEBUG_INFO("argument:%s",argv[optind]);
}
return 0;
}
执行性结果:
$ _build_/argopt -i 192.168.0.5 -p 1502
/big/work/ipc/argopt.c - 19 - main :: option:i ip = 192.168.0.5
/big/work/ipc/argopt.c - 22 - main :: option:p port = 1502
/big/work/ipc/argopt.c - 32 - main :: optind = 5
现在IP地址和端口的字符串已经获取到了,现在就是判断这两个参数是否合法了。
判断IP地址合法性,如下,(啥?有BUG?我的四十米大刀呢?)
int is_valid_ip(const char *ipstr){
int res = 0;
int ip[4];
DEBUG_INFO("ipstr =%s",ipstr);
sscanf(ipstr, "%d.%d.%d.%d", &ip[0],&ip[1],&ip[2],&ip[3]);
for(int i = 0; i < 4; i++){
printf("%d,",ip[i]);
if(ip[i] > 255 || ip[i] < 0){
res = -1;
}
}
printf("\n");
return res;
}
使用atoi函数判断端口号,首先知道atoi能解析什么样的字符串,并且解析到什么程度,如下代码:
#define DEBUG_IF(format, ...) printf(""format"\n" ,##__VA_ARGS__)
#define DEBUG_IF(format, ...) printf(""format"\n" ,##__VA_ARGS__)
void test_02(){
int a;
a = atoi("1234");
DEBUG_IF("a = %d",a);
a = atoi("a1234");
DEBUG_IF("a = %d",a);
a = atoi("1234a");
DEBUG_IF("a = %d",a);
a = atoi("12a34");
DEBUG_IF("a = %d",a);
a = atoi("+1234");
DEBUG_IF("a = %d",a);
a = atoi("-1234");
DEBUG_IF("a = %d",a);
}
输出结果:
a = 1234
a = 0
a = 1234
a = 12
实验解析
情况1:“1234”合法,返回值是1234
情况2:"a1234",第一个字符是非法字符串,结果返回0
情况3:"1234a",最后一个字符是非法字符串,结果返回1234
情况4:"12a34",中间出现一个非数字字符,返回非数字字符前面的数字。
总之呢,使用之前先测下吧。
情况5和6:能够识别正好“+”和负号“-”。
整理后的代码如下所示:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
#define DEFAULT_IP "0.0.0.0"
#define DEFAULT_PORT ((uint16_t)502)
char gip[20];
uint16_t gport;
int is_valid_ip(const char *ipstr){
int res = 0;
int ip[4];
DEBUG_INFO("ipstr =%s",ipstr);
sscanf(ipstr, "%d.%d.%d.%d", &ip[0],&ip[1],&ip[2],&ip[3]);
for(int i = 0; i < 4; i++){
printf("%d,",ip[i]);
if(ip[i] > 255 || ip[i] < 0){
res = -1;
}
}
printf("\n");
return res;
}
int is_valid_port(const char *portstr){
int port = atoi(portstr);
if(port < 0 || port > 65535){
return -1;
}
return port;
}
void test_01(int argc,char **argv){
int opt;
int temp;
while((opt = getopt(argc,argv,"i:p:")) != -1){
switch (opt)
{
case 'i':
DEBUG_INFO("option:%c ip = %s",opt,optarg);
memset(gip,0,sizeof(gip));
if(is_valid_ip(optarg) == 0){
memcpy(gip,optarg,strlen(optarg));
}else{
memcpy(gip,DEFAULT_IP,strlen(DEFAULT_IP));
}
break;
case 'p':
DEBUG_INFO("option:%c port = %s",opt,optarg);
temp = is_valid_port(optarg);
if(temp < 0){
gport = DEFAULT_PORT;
}else{
gport = temp;
}
break;
case '?':
DEBUG_INFO("unkown option:%c needs a parameter",optopt);
break;
default:
DEBUG_INFO("unkown option:%c ",optopt);
break;
}
}
DEBUG_INFO("配置的IP地址:%s,和端口%d",gip,gport);
}
#define DEBUG_IF(format, ...) printf(""format"\n" ,##__VA_ARGS__)
int main(int argc,char **argv)
{
test_01(argc,argv);
// test_02();
return 0;
}
测试效果:
$ ./argopt -i 192.168.5.110 -p 1502
/big/work/ipc/argopt.c - 44 - test_01 :: option:i ip = 192.168.5.110
/big/work/ipc/argopt.c - 17 - is_valid_ip :: ipstr =192.168.5.110
192,168,5,110,
/big/work/ipc/argopt.c - 53 - test_01 :: option:p port = 1502
/big/work/ipc/argopt.c - 69 - test_01 :: 配置的IP地址:192.168.5.110,和端口1502
如果是这样呢:./argopt -i .168.5.110 -p 1502,最后输出的结果就是下面这样的
/big/work/ipc/argopt.c - 69 - test_01 :: 配置的IP地址:0.0.0.0,和端口1502
getopt_long函数
许多Linux应用程序也接受比我们在前面例子中所用的单字符选项含义更明确的参数。GNU C函数库包含getopt的另一个版本,称作getopt_long,它接受以双划线(--)开始的长参数。
#include <unistd.h>
int getopt(int argc, char * const argv[],
const char *optstring);
extern char *optarg;
extern int optind, opterr, optopt;
#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);
重写前面的代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
#define DEFAULT_IP "0.0.0.0"
#define DEFAULT_PORT ((uint16_t)502)
char gip[20];
uint16_t gport;
int is_valid_ip(const char *ipstr){
int res = 0;
int ip[4];
DEBUG_INFO("ipstr =%s",ipstr);
sscanf(ipstr, "%d.%d.%d.%d", &ip[0],&ip[1],&ip[2],&ip[3]);
for(int i = 0; i < 4; i++){
printf("%d,",ip[i]);
if(ip[i] > 255 || ip[i] < 0){
res = -1;
}
}
printf("\n");
return res;
}
int is_valid_port(const char *portstr){
int port = atoi(portstr);
if(port < 0 || port > 65535){
return -1;
}
return port;
}
void test_01(int argc,char **argv){
int opt;
int temp;
DEBUG_INFO("no_argument = %d",no_argument);
DEBUG_INFO("required_argument = %d",required_argument);
DEBUG_INFO("optional_argument = %d",optional_argument);
struct option long_options[] ={
{"ip",required_argument,NULL,'i'},
{"port",required_argument,NULL,'p'},
{0,0,0,0}
};
memcpy(gip,DEFAULT_IP,strlen(DEFAULT_IP));
gport = DEFAULT_PORT;
while((opt = getopt_long(argc,argv,"i:p:",long_options,NULL)) != -1){
switch (opt)
{
case 'i':
DEBUG_INFO("option:%c ip = %s",opt,optarg);
memset(gip,0,sizeof(gip));
if(is_valid_ip(optarg) == 0){
memcpy(gip,optarg,strlen(optarg));
}else{
memcpy(gip,DEFAULT_IP,strlen(DEFAULT_IP));
}
break;
case 'p':
DEBUG_INFO("option:%c port = %s",opt,optarg);
temp = is_valid_port(optarg);
if(temp < 0){
gport = DEFAULT_PORT;
}else{
gport = temp;
}
break;
case '?':
DEBUG_INFO("unkown option:%c needs a parameter",optopt);
break;
default:
DEBUG_INFO("unkown option:%c ",optopt);
break;
}
}
DEBUG_INFO("配置的IP地址:%s,和端口%d",gip,gport);
}
#define DEBUG_IF(format, ...) printf(""format"\n" ,##__VA_ARGS__)
int main(int argc,char **argv)
{
test_01(argc,argv);
// test_02();
return 0;
}
测试结果:
$ ./argopt2 --ip 192.168.5.110 --port 1502
/big/work/ipc/argopt2.c - 41 - test_01 :: no_argument = 0
/big/work/ipc/argopt2.c - 42 - test_01 :: required_argument = 1
/big/work/ipc/argopt2.c - 43 - test_01 :: optional_argument = 2
/big/work/ipc/argopt2.c - 56 - test_01 :: option:i ip = 192.168.5.110
/big/work/ipc/argopt2.c - 18 - is_valid_ip :: ipstr =192.168.5.110
192,168,5,110,
/big/work/ipc/argopt2.c - 65 - test_01 :: option:p port = 1502
/big/work/ipc/argopt2.c - 81 - test_01 :: 配置的IP地址:192.168.5.110,和端口1502
实验解析
getopt_long函数比getopt多两个参数。第一个附加参数是一个结构数组,它描述了每个长选项并告诉getopt_long如何处理它们。第二个附加参数是一个变量指针,它可以作为optind的长选项版本使用。对于每个识别的长选项,它在长选项数组中的索引就写入该变量。在本例中,你不需要这一信息,因此第二个附加参数是NULL。
长选项数组由一些类型为struct option的结构组成,每个结构描述了一个长选项的行为。该数组必须以一个包含全0的结构结尾。
长选项结构在头文件getopt.h中定义,并且该头文件必须与常量_GNU_SOURCE一同包含进来,该常量启用getopt_long功能。我GNU编译器中,_GNU_SOURCE选项默认是已经定义的。
struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
该结构的成员下如所示。
要了解GNU对getopt扩展的其他选项及相关函数,请参考getopt的手册页。
小结
是不是觉得有BUG,想想linux标准中,经常看到的类似一句话,未经定义的输入,会产生不会预知的输出。我只需要保证未经定义的输入不会让程序死掉就行了。这很难吗,不简单。