本文示例可以解析的配置文件为key-value格式,key与value之间可以使用空白符、等号及冒号分隔。
配置文件示例如下:
# test.conf
nameserver 114.114.114.114
nameserver 8.8.8.8
DOMAIN=lab.foo.com
domain=bar.foo.com
search lab.foo.com
search bar.foo.com lab.example.com
DNS1=114.114.114.114
DNS2=8.8.8.8
test:/usr/bin:/root/bin
test:/home/test:/bin/bash
数据结构定义的头文件:
// config.h
/* 该配置项只能有一个,重复会报错 */
#define CONF_SINGLE 1
/* 该配置项可以设置多个且都有效,如设置DNS的nameserver */
#define CONF_MULTIPLE 2
/* 配置文件中,第一个配置项有效,忽略后面重复的配置项 */
#define CONF_FIRST_VALID 3
/* 配置文件中,最后一个配置项有效,忽略前面重复的配置项 */
#define CONF_LAST_VALID 4
#define MAX_BUF_SIZE 1024
typedef struct conf_s
{
const char *key; /* conf keyword */
const int type;
const char *connector; /* join fields by connector characters */
char *value;
} conf_t;
int parse(FILE *fp, conf_t *cf);
解析函数实现:
// config.c
#include
#include
#include
#include
#include "config.h"
char *trim_left_right(char *s)
{
char *e;
/* 去除开头的空白 */
while (isspace(*s)) s++;
/* 结尾空白全部置为\0 */
e = s + strlen(s) - 1;
while (isspace(*e) && e > s) {
*e = '\0';
e--;
}
if (e == s) {
*e = '\0';
}
return s;
}
int isdelimiter(char c)
{
if (isspace(c) || c == '=' || c == ':' ) {
return 1;
} else {
return 0;
}
}
int set_conf(conf_t *conf, char *key, char *value)
{
conf_t *p;
if (key == NULL || value == NULL || conf == NULL) {
return 0;
}
for (p = conf; p->key != NULL; p++)
{
if (strcasecmp(key, p->key) == 0 && p->value) {
switch (p->type) {
case CONF_MULTIPLE:
if (*p->value != '\0') {
/* 存在重复项时,使用connector拼接 */
sprintf(p->value, "%s%s%s", p->value, p->connector, value);
} else {
sprintf(p->value, "%s", value);
}
break;
case CONF_SINGLE:
if (*p->value != '\0') {
printf("ERROR: keyword \"%s\" is duplicate\n", key);
return 0;
} else {
sprintf(p->value, "%s", value);
}
break;
case CONF_FIRST_VALID:
if (*p->value == '\0') {
sprintf(p->value, "%s", value);
}
break;
case CONF_LAST_VALID:
sprintf(p->value, "%s", value);
break;
}
return 1;
}
}
printf("ERROR: unknown keyword \"%s\"\n", key);
return 0;
}
int parse(FILE *fp, conf_t *cf)
{
char buf[MAX_BUF_SIZE], *key, *value, *tmp;
memset(buf, 0, MAX_BUF_SIZE);
while (fgets(buf, MAX_BUF_SIZE, fp) != NULL)
{
/* 去除#号及其之后的字符 */
tmp = buf;
while (*tmp != '#' && *tmp != '\0') {
tmp++;
}
if (*tmp == '#') {
*tmp = '\0';
}
/* 去除前后的空白符 */
key = trim_left_right(buf);
if (*key == '\0') {
memset(buf, 0, MAX_BUF_SIZE);
continue;
}
/* 使用\0设置key和value之间的分隔符,即可取出key,并得到value的起始位置 */
value = key;
while (!isdelimiter(*value) && *value != '\0') {
value++;
}
while (isdelimiter(*value) && *value != '\0') {
*value = '\0';
value++;
}
if (*value == '\0') {
printf("ERROR: no value for keyword \"%s\"\n", key);
memset(buf, 0, MAX_BUF_SIZE);
continue;
}
if (!set_conf(cf, key, value)) {
return 0;
}
memset(buf, 0, MAX_BUF_SIZE);
}
return 1;
}
解析函数调用及测试:
// main.c
#include
#include
#include
#include "config.h"
#define MAX_VALUE_SIZE 64
int main(int argc, char *argv[])
{
int i;
FILE *fp;
char *conffile;
char value[6][MAX_VALUE_SIZE];
conf_t conf[] = {
{"nameserver", CONF_MULTIPLE, " ", value[0]},
{"domain", CONF_LAST_VALID, NULL, value[1]},
{"search", CONF_LAST_VALID, NULL, value[2]},
{"dns1", CONF_SINGLE, NULL, value[3]},
{"dns2", CONF_SINGLE, NULL, value[4]},
{"test", CONF_FIRST_VALID, NULL, value[5]},
{NULL, 0, NULL, NULL}
};
if (argc == 2) {
conffile = argv[1];
} else {
conffile = "test.conf";
}
if ((fp = fopen(conffile, "r")) == NULL)
{
fprintf(stderr, "ERROR: can't open conf file \"%s\"\n", conffile);
exit(1);
}
for (i=0; i<6; i++) {
memset(value[i], 0, MAX_VALUE_SIZE);
}
if (!parse(fp, conf)) {
fclose(fp);
exit(1);
}
for (i=0; i<6; i++) {
if (strlen(conf[i].value)) {
printf("%s: [%s]\n", conf[i].key, conf[i].value);
}
}
fclose(fp);
return 0;
}
Makefile:
LIBS= -lm
CFLAGS= -Wall -O2 -g
OBJS= main.o config.o
main: $(OBJS)
gcc -o $@ $(OBJS) $(LIBS)
clean:
rm -f main $(OBJS)
编译及测试:
# make
cc -Wall -O2 -g -c -o main.o main.c
cc -Wall -O2 -g -c -o config.o config.c
gcc -o main main.o config.o -lm
# ./main
nameserver: [114.114.114.114 8.8.8.8]
domain: [bar.foo.com]
search: [bar.foo.com lab.example.com]
dns1: [114.114.114.114]
dns2: [8.8.8.8]
test: [/usr/bin:/root/bin]