前言
就喜歡使用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。
參考學習:https://www.cnblogs.com/NickQ/p/11368656.html
https://blog.csdn.net/techhome803/article/details/8507839
域名解析函數
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地址訪問百度:
可以成功訪問。