前言
就喜欢使用linux系统的用户来说,命令行终端对于我们来说再熟知不过了,用户通过使用命令和系统进行交互,Linux命令的格式一般是命令 + 选项 + 参数,当输入命令的时候,shell通过解析参数而让内核执行不同的功能。这篇文章记录一下我学习命令行参数解析的内容,并编写一个服务器ip地址解析的小程序,为后期做项目做好准备。😀
GNU C提供的参数解析函数
getopt和getopt_long头文件都是include <getopt.h>。
getopt
int getopt(int argc, char * const argv[],const char *optstring);
参数解析:
argc:命令行参数的个数
argv:保存命令参数的字符串数组。其类型不是很清楚,所以学习一下:
- char * const ptr:可以将其看成 char *(const ptr),所以ptr是一个常量指针,不可以修改指针的值,但是可以修改指针所指向的内容。
- char const *ptr 和 const char *ptr :指向字符常量的指针,可以对指针修改,但是不可以改变指针所指的内容。
- 另外,argv还是一个二级指针。
optstring:命令行选项声明,比如终端下"cp -r file/ file1/","-r"就是段选项,在函数参数用a:b::cd:
表示命令是否带参数:
- a:后面根一个冒号,意思是选项a后面带有参数。
- b::后面跟两个冒号,意思是选项b后面可以带参数也可以不带参数。如果要带参数,参数和冒号之间不能有空格,比如:-b123456
- c后面没有冒号,意思是c选项不带参数。
- 全局变量chat *optarg:指向当前选项后面的参数。比如:-b123456,*optarg = 123456。所以在编写参数解析功能模块时,不需要定义就可以直接使用。
返回值:所有参数解析完毕,返回-1。
getopt_long
Linux下不仅有短选项,如gcc -v,还有长选项如gcc --version,getopt只能处理短选项,getopt_long弥补了它的缺陷,可以处理长选项,比getopt多了两个参数。
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex);
参数解析:
argc:命令行参数的个数
argv:保存命令参数的字符串数组。
optstring:命令行短选项声明。
longopts:结构体数组,用于描述长选项的解析方式。
struct option
{
const char *name;
int has_arg;
int *flag;
int val;
};
const char *name:选项的名称譬如“help”。
int has_arg:描述了选项是否有选项参数。
0 no_argument 选项没有参数
1 required_argument 选项需要参数
2 optional_argument 选项参数可选
int *flag 和 int val 这两个参数有关联,如果这个指针为NULL,那么getopt_long()返回该结构val字
段中的数值。如果该指针不为NULL,getopt_long()会使得它所指向的变量中填入val字段中的数值,并
getopt_long()返回0。如果flag不是NULL,但未发现长选项,那么它所指向的变量的数值不变。
longindex:结构体数组的下标,我们一般对其不关心,直接填NULL。
域名解析函数
struct hostent* gethostbyname(const char *name);
参数解析:
- name:服务器域名地址
返回:解析完成返回一个hostent结构体。
struct hostent
{
char *h_name;//主机规范名
char ** h_aliases;//别名
short h_addrtype;//主机IP地址的类型ipv4/ipv6
short h_length;//主机ip地址的长度
char ** h_addr_list;//表示的是主机的ip地址,
//这个是以网络字节序存储的。
//需要调用inet_ntop();
};
一个简单的例子
功能实现说明:通过对命令行参数解析,实现解析百度域名。
- 如果用私有ip地址访问服务器,必须指定ip和端口,也就是执行程序时要带上选项-i + IP地址 -p + 端口号。
- 如果用域名访问则只需带选项-d + 域名。
- 如果带选项-h,打印帮助信息。
- 带其他选项,或者不带选项,程序打印帮助信息
#include<stdio.h>
#include<getopt.h>
#include<stdlib.h>
#deine ALL_IP 0
//函数声明
static void print_usage(char *order);
char* DNS(char *domain);
int argument_parse(int argc,char **argv,char **serv_IP,int *serv_port);
int main(int argc,char **argv)
{
char *serv_IP = NULL;//服务器IP
int serv_port = 0;//服务器端口
int rv = -1;//函数返回值
rv = argument_parse(argc,argv,&serv_IP,&serv_port);//参数解析
if(0 == rv)
{
printf("解析出服务器serv_IP为:%s\n",serv_IP);
printf("服务器端口为:%d\n",serv_port);
}
return 0;
}
/*功能:打印帮助信息
* 参数:order:执行的程序名
* 返回值:无*/
static void print_usage(char *order)
{
printf("%s usages:\n",order);
printf("-I(IP):server IP\n");
printf("-p(port):server port\n");
printf("-d(domain):domain name prase\n");
printf("-h(help):help information\n");
}
/*功能:域名解析
*参数:domain:传入域名
*返回:返回一个点分十进制IP*/
char* DNS(char *domain)
{
struct hostent *host = (struct hostent *)malloc(sizeof(struct hostent));//保存解析之后的ip地址信息
if(!domain)
{
return NULL;
}
host = gethostbyname(domain);//域名解析函数
if(!host)
{
return NULL;
}
//因为有的域名地址关联着很多ip地址,所以可以通过修改宏ALL_IP用for循环依次打印
#if ALL_IP
for(int i = 0; host->h_addr_list[i]; i++)
{
printf("%d ip:%s\n",i,inet_ntoa(*(struct in_addr*)host->h_addr[i]));
}
#endif
return inet_ntoa(*(struct in_addr*)host->h_addr);//返回一个点分十进制的IP指针
}
/* 功能:参数解析
* 参数:argc:参数个数
* argv:保存参数的字符串指针
* serv_IP:服务器ip(output)
* serv_port:服务器端口(output)
* 返回值:成功返回0,错误返回-1*/
int argument_parse(int argc,char **argv,char **serv_IP,int *serv_port)
{
int optopt;//getopt返回值
int domain_mark = 0;//域名解析标志
//定义选项处理方案
struct option long_options[]=
{
{"IP",1,NULL,'i'},
{"port",1,NULL,'p'},
{"help",0,NULL,'h'},
{0,0,0,0}
};
while((optopt=getopt_long(argc,argv,"i:p:d:h",long_options,NULL)) > 0)
{
switch(optopt)
{
case 'i':
*serv_IP = optarg;
break;
case 'p':
*serv_port = atoi(optarg);
break;
case 'd':
*serv_IP = DNS(optarg);//域名解析
domain_mark = 1;//表示进行了域名解析
printf("serv_ip=%s\n",*serv_IP);
break;
case 'h':
print_usage(argv[0]);
break;
default:
break;
}
}
if(!(*serv_IP)||(!serv_port && !domain_mark))
{
print_usage(argv[0]);
return -1;
}
return 0;
}
编译测试结果:
用这个ip地址访问百度:
可以成功访问。