C语言解析配置文件的函数

本文示例可以解析的配置文件为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]

你可能感兴趣的:(C语言解析配置文件的函数)