在linux平台下的较为庞大的命令一般都带有一个配置文件,用于存储该命令启动时要设置的参数,用户还可以变更该配置文件中的某些域的值。因此,在命令中就要考虑怎么来存取这些文件里的值。一般情况下,大多数程序员都愿意自己编写一段程序来解析配置文件里内容,在配置文件比较小的情况下,该中方法也非常方便适用,我平时也喜欢这么作。但是,当在配置文件非常大情况下,配置文件里面的section,field非常多的情况下,自己编写程序来读取的话就变得非常麻烦和挠头了。幸亏,linux下还有lex/yacc这么一套工具来帮助程序员来完成这样的工作。 在互联网上,搜索了lex,yacc这两个关键字,可以发现很多的相关文章,但这些文章大都是千篇一律,讲了那么一大堆理论,看完之后还是不知道在C程序里怎么使用lex/yacc。只有这些文章《Lex和Yacc从入门到精通》还比较贴近实际编码,对我有一些帮助,建议大家可以先看看。 在这里,我就从配置文件解析这个日常编程经常要碰到的问题来作为例子,来告诉大家lex/yacc这玩意儿的使用方法。 首先,说明一下大概的原理。先看一下下面的图. 注:本文中的图摘自《Lex and Yacc for Embedded Programmers》
Figure1: Execution path and data flow in a typical lex/yacc derived parser 图1显示了在一个典型的扫描器/解析器的应用中的控制和数据流程。这个数据流通过扫描器规约成很多符号并且标识成有效的符号组合聚集在解析器中。也就是说,输入流(或者说是配置文件)中的所有单词通过lex工具的扫描生成很多的符号(token)--- 就是资料上常说的词法分析,然后,这些符号再通过yacc工具进行语法解析(按照,编译原理中的巴克斯范式)。 图2描述了一个基于lex/yacc的应用程序的流程。一般情况下,做成一个读取配置文件的程序都要包含两个脚本文件,第一个是lex脚本文件(文件名的后缀一般都是.l形式,不过文件名的形式都没有作特别严格的定义),该脚本文件主要是利用正则表达式的形式对文件进行对配置文件进行正则匹配,这一点非常类似linux的awk工具。另外一个脚本就是yacc脚本(后缀名一般为.y形式),它主要是根据lex的分析结果进行语法解析。
Figure 2: Development sequence when using lex and yacc 图3表示了配置文件解析的数据流。正如下图描述的那样,配置文件里的内容首先是作为字符流的形式,通过lex的yylex函数进行正则匹配后形成许多符号,然后作为yacc的输入,通过yyparse函数对这些符号流进行语法解析。
在上面提到的lex脚本,yacc脚本文件作成后,只需要在你自己的C程序中,手动调用yacc的yyparse函数就可以了,只不过,还需要在C程序中添加一些存储yacc解析结果的处理函数,这得根据程序中的实际情况来定。 下面是两个脚本文件和一个C程序文件的样例:http://lymons./yacc.html lex脚本: test.conf.l yacc脚本:test.conf.y c源文件:test_conf.c test_conf.c第二版 编译方法及执行: cvs parser #lex test_config.l cvs parser #yacc -d test_config.y cvs parser #cc lex.yy.c y.tab.c test_config.c -o test_config<!--[endif]--> cvs parser # ./test_config test.conf.example用lex/yacc来解析配置文件---test.conf.l %{ #include <string.h> #include "y.tab.h" extern int line; //当前正在解析字符串的行号 %} %% //下面定义配置文件里各关键字的正则,注意各关键字返回的token必须是在yacc的脚本文件中有定义 //首先是定义section标题和field名的正则表达式 share return T_SHARE; temp return T_TEMP; public return T_PUBLIC; comment return T_COMMENT; writeable return T_WRITEABLE; path return T_PATH; guest[ ]ok return T_GUESTOK; valid[ ]users return T_VALID_USERS; write[ ]list return T_WRITE_LIST; create[ ]mode return T_CREATE_MODE; directory[ ]mask return T_DIRECTORY_MODE; <!--[if !supportEmptyParas]--> <!--[endif]--> //其次是field的值的表达式 y|Y|yes|Yes|YES yylval.string = strdup(yytext); return T_STATE; n|N|no|No|NO yylval.string = strdup(yytext); return T_STATE; \"[^\n]+\" yylval.string = strdup(yytext); return T_STRING; //要求字符串以""为开头和结尾 [0-9]+ yylval.number = strtol(yytext,NULL,8); return T_NUMBER; //因为是权限,故要转化成8进制 \n line++; //遇到回车行号加一 [ \t]+ ; //忽略空格和tab \#[^\n]* ; //忽略#开头的行 \;[^\n]* ; //忽略;开头的行 . return (int)yytext[0]; 用lex/yacc来解析配置文件---test.conf.y //test.conf.y %{ void section_print(int); void parameter_print(int); %} //定义每个关键字对应的token %token T_SHARE %token T_TEMP %token T_PUBLIC %token T_COMMENT %token T_WRITEABLE %token T_PATH %token T_GUESTOK %token T_VALID_USERS %token T_WRITE_LIST %token T_CREATE_MODE %token T_DIRECTORY_MODE //定义配置文件里field值的基本类型,一般有两种;数字和字符串 %union { int number; char *string; } %token <string> T_STRING //普通字符串,例如路径 %token <number> T_NUMBER //数字,例如端口号 %token <string> T_STATE //状态,例如yes,no //定义语法解析树 //为每一个section标题和field名定义相应的动作(函数) //该动作(函数)的实现在.c文件里定义. %% parameters: | parameters parameter ; parameter: section_share | comment | writeable | path | guestok | public | valid_users | write_list | section_temp | create_mode | directory_mode ; section_share: '[' T_SHARE ']' { section_print(T_SHARE); } ; comment: T_COMMENT '=' T_STRING { parameter_print(T_COMMENT); } ; writeable: T_WRITEABLE '=' T_STATE { parameter_print(T_WRITEABLE); } ; path: T_PATH '=' T_STRING { parameter_print(T_PATH); } ; guestok: T_GUESTOK '=' T_STATE { parameter_print(T_GUESTOK); } ; public: T_PUBLIC '=' T_STATE { parameter_print(T_PUBLIC); } ; valid_users: T_VALID_USERS '=' T_STRING { parameter_print(T_VALID_USERS); } ; write_list: T_WRITE_LIST '=' T_STRING { parameter_print(T_WRITE_LIST); } ; section_temp: '[' T_TEMP ']' { section_print(T_TEMP); } ; create_mode: T_CREATE_MODE '=' T_NUMBER { parameter_print(T_CREATE_MODE); } ; directory_mode: T_DIRECTORY_MODE '=' T_NUMBER { parameter_print(T_DIRECTORY_MODE); } ; %% 用lex/yacc来解析配置文件---test_conf.c //test_conf.c #include <stdio.h> #include <string.h> #include <errno.h> #include "y.tab.h" extern FILE *yyin; int line = 1; //定义当前正在解析字符串的行号 void yyerror(const char *str) { fprintf(stderr,"config: parse error at %d", line); fprintf(stderr," : %s\n",str); exit(1); } int yywrap() { return 1; } int main(int argc, char **argv) { if (argc > 1) { yyin = fopen(argv[1], "r"); } else { fprintf(stdout, "Usage: config <file_path>\n"); exit(1); } if (yyin == NULL) { fprintf(stdout, "config: file access error. func=%s ", "fopen()"); fprintf(stdout, "errno=%d(%s) name=%s\n", errno, strerror(errno), argv[0]); exit(1); } printf("### %s ###\n", argv[1]); yyparse(); fclose(yyin); exit(0); } <!--[if !supportEmptyParas]--> <!--[endif]--> //定义当解析到每一个section标题时,yacc所采取的动作. void section_print(int section) { static int ishare = 0; static int itemp = 0; switch (section) { case T_SHARE: ishare++; printf("[share:%d]\n", ishare); break; case T_TEMP: itemp ++; printf("[temp:%d]\n", itemp); break; default: break; } } <!--[if !supportEmptyParas]--> <!--[endif]--> //定义解析到每一个field时,yacc所采取的动作 void parameter_print(int parameter) { switch (parameter) { case T_PUBLIC: printf("\tpublic = %s\n", yylval.string); break; case T_COMMENT: printf("\tcomment = %s\n", yylval.string); break; case T_WRITEABLE: printf("\twriteable = %s\n", yylval.string); break; case T_PATH: printf("\tpath = %s\n", yylval.string); break; case T_GUESTOK: printf("\tguest ok = %s\n", yylval.string); break; case T_VALID_USERS: printf("\tvalid users = %s\n", yylval.string); break; case T_WRITE_LIST: printf("\twrite list = %s\n", yylval.string); break; case T_CREATE_MODE: printf("\tcreate mode = %o\n", yylval.number); break; case T_DIRECTORY_MODE: printf("\tdriectory mask = %d\n", yylval.number); break; default: break; } } yacc读取配置文件内容 --- c源文件改进版 为了读取配置文件某一个区域的数据,特在C源文件里 把读取某区域的动作封装成一个函数,方便调用. 例如:read_share_section,read_server_section 注意yylval.string的内存实在test_conf.l文件中生成的, 所以在parameter_print使用完毕后要释放该内存 因为该程序事先把配置文件中的内容按照类型存储到 全局变量的数组里,等到用户指定要读取那一段区域 的内容是在把该段的内容返回给用户. 因为说明的重点并不是怎么释放内存, 程序里并没有加入释放该全局数组的内存的代码. 1#include <stdio.h> 2#include <string.h> 3#include <stdlib.h> 4#include <errno.h> 5#include "y.tab.h" 6 7extern FILE *yyin; 8 9int line = 1; 10 11void yyerror(const char *str) { 12 fprintf(stderr,"config: parse error at %d", line); 13 fprintf(stderr," : %s\n",str); 14 exit(1); 15} 16 17int yywrap() { 18 return 1; 19} 20 21#define LEN 129 22#define UGNUM 128 23#define NAMELEN 9 24#define PASSLEN 17 25#ifndef PATH_MAX 26#define PATH_MAX 4096 27#endif 28 29typedef struct share_section { 30 char comment[LEN]; 31 int writeable; 32 char path[PATH_MAX]; 33 int guestok; 34 int public; 35 int create_mode; 36 int directory_mask; 37 char *valid_users; 38 char *write_list; 39}share_section_t; 40 41typedef struct server_section { 42 char comment[LEN]; 43 char hostname[LEN]; 44 char ipaddr[16]; 45 char loginname[NAMELEN]; 46 char loginpass[PASSLEN]; 47 int port; 48 char encoding[NAMELEN]; 49} server_section_t; 50 51 52share_section_t *share_sections[16]; 53server_section_t *server_sections[16]; 54int ishare = 0; 55int iserver = 0; 56 57int main(int argc, char **argv) { 58 share_section_t share; 59 server_section_t server; 60 61 if (argc > 1) { 62 yyin = fopen(argv[1], "r"); 63 } else { 64 fprintf(stdout, "Usage: config <file_path>\n"); 65 exit(1); 66 } 67 if (yyin == NULL) { 68 fprintf(stdout, "config: file access error. func=%s ", "fopen()"); 69 fprintf(stdout, "errno=%d(%s) name=%s\n", errno, strerror(errno), argv[0]); 70 exit(1); 71 } 72 73 yyparse(); 74 75 fclose(yyin); 76 77 read_share_section(0, &share);//读取配置文件中第一个share区的内容. 78 79 exit(0); 80} 81 82void section_print(int section) { 83 84 switch (section) { 85 case T_SHARE: 86 if (ishare > 16) 87 return; 88 share_sections[ishare ++] = 89 (share_section_t *)malloc(sizeof(share_section_t)); 90 91 //printf("[share:%d]\n", ishare); 92 break; 93 case T_SERVER: 94 if (iserver > 16) 95 return; 96 server_sections[iserver ++] = 97 (server_section_t*)malloc(sizeof(server_section_t)); 98 //printf("[temp:%d]\n", itemp); 99 break; 100 default: 101 break; 102 } 103} 104 105void parameter_print(int parameter) { 106 if (ishare <= 0 || ishare > 16 || iserver <= 0 || iserver > 16) 107 return; 108 109 share_section_t *sharesec = share_sections[ishare-1]; 110 server_section_t *serversec = server_sections[iserver-1]; 111 112 switch (parameter) { 113 case T_PUBLIC: 114 // printf("\tpublic = %s\n", yylval.string); 115 sharesec->public = yylval.number; 116 break; 117 case T_COMMENT: 118 //printf("\tcomment = %s\n", yylval.string); 119 strncpy(sharesec->comment, yylval.string, LEN); 120 free(yylval.string); 121 break; 122 case T_WRITEABLE: 123 //printf("\twriteable = %s\n", yylval.string); 124 sharesec->writeable = yylval.number; 125 break; 126 case T_PATH: 127 //printf("\tpath = %s\n", yylval.string); 128 strncpy(sharesec->path, yylval.string, PATH_MAX); 129 free(yylval.string); 130 break; 131 case T_GUESTOK: 132 //printf("\tguest ok = %s\n", yylval.string); 133 sharesec->guestok = yylval.number; 134 break; 135 case T_VALID_USERS: 136 //printf("\tvalid users = %s\n", yylval.string); 137 sharesec->valid_users = strdup(yylval.string); 138 free(yylval.string); 139 break; 140 case T_WRITE_LIST: 141 //printf("\twrite list = %s\n", yylval.string); 142 sharesec->write_list = strdup(yylval.string); 143 free(yylval.string); 144 break; 145 case T_CREATE_MODE: 146 //printf("\tcreate mode = %o\n", yylval.number); 147 sharesec->create_mode = yylval.number; 148 break; 149 case T_DIRECTORY_MODE: 150 //printf("\tdriectory mask = %d\n", yylval.number); 151 sharesec->directory_mask = yylval.number; 152 break; 153 case T_HOSTNAME: 154 strncpy(serversec->hostname, yylval.string, LEN); 155 free(yylval.string); 156 break; 157 case T_IPADDR: 158 strncpy(serversec->ipaddr, yylval.string, 15); 159 free(yylval.string); 160 break; 161 case T_LOGINNAME: 162 strncpy(serversec->loginname, yylval.string, NAMELEN); 163 free(yylval.string); 164 break; 165 case T_LOGINPASS: 166 strncpy(serversec->loginpass, yylval.string, NAMELEN); 167 free(yylval.string); 168 break; 169 case T_PORT: 170 serversec->port = yylval.number; 171 break; 172 case T_ENCODE: 173 strncpy(serversec->encoding, yylval.string, NAMELEN); 174 free(yylval.string); 175 break; 176 default: 177 break; 178 } 179} 180 181int read_share_section(int offset, share_section_t *section) 182{ 183 if (offset >= ishare) 184 return -1; 185 186 section = share_sections[offset]; 187 188 return 0; 189} 190 191int read_server_section(int offset, server_section_t *section) 192{ 193 if (offset >= iserver) 194 return -1; 195 196 section = server_sections[offset]; 197 198 return 0; 199} 200 |
|
来自: SamBookshelf > 《工具》