14、配置文件解析实战:解析ini或conf格式的配置文件

说实话,配置文件解析这事儿,看着简单,坑却不少。

我早年做嵌入式项目时,就吃过配置文件的亏。当时设备上电后总读不到正确的参数,排查了半天,发现是配置文件里多了一个空格。嗯,从那以后,我对解析这件事就格外上心。

今天咱们就来聊聊,怎么用C语言写一个靠谱的ini/conf文件解析器。

14.1 什么是ini/conf配置文件

说白了,ini文件就是一种结构简单的文本文件。它用节(section)、键(key)、值(value)来组织数据。你想想看,很多软件的设置界面背后,其实就是这种文件在撑着。

典型的格式长这样:

; 这是注释(分号开头)
[network]
ip = 192.168.1.100
port = 8080
timeout = 30

[database]
host = localhost
user = admin
password = 123456

我个人习惯把这种文件叫做“键值对+分组”的存储方式。每个[section]下面可以挂一堆key=value

14.2 解析的核心思路

解析配置文件,说白了就是逐行读文件,然后做三件事:

  • 跳过空行和注释——别让无关内容干扰你
  • 识别节名——看到方括号就知道新节开始了
  • 提取键值对——找到等号,左右一拆就完事

我在项目中遇到过一种情况:有人把等号两边加了空格,比如ip = 192.168.1.100。如果你直接按等号切分,左边会多出个空格。所以,记得做trim(去除首尾空白)。

核心原则:解析器要足够健壮,能容忍用户手抖多打的空格和空行。

14.3 代码实现:一个轻量级解析器

下面是我写的一个简易解析器。它不依赖任何第三方库,纯C语言实现。你直接拿去用就行。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_LINE 256
#define MAX_SECTION 64
#define MAX_KEY 64
#define MAX_VALUE 256

// 去除字符串首尾空白
void trim(char *str) {
    char *start = str;
    char *end = str + strlen(str) - 1;

    while (*start == ' ' || *start == '\t') start++;
    while (end > start && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) end--;

    *(end + 1) = '\0';
    // 如果开头有移动,整体前移
    if (start != str) {
        memmove(str, start, end - start + 2);
    }
}

// 解析配置文件
int parse_ini(const char *filename) {
    FILE *fp = fopen(filename, "r");
    if (!fp) {
        printf("无法打开文件: %s\n", filename);
        return -1;
    }

    char line[MAX_LINE];
    char current_section[MAX_SECTION] = "";
    int line_num = 0;

    while (fgets(line, sizeof(line), fp)) {
        line_num++;
        trim(line);

        // 跳过空行和注释
        if (line[0] == '\0' || line[0] == ';' || line[0] == '#') {
            continue;
        }

        // 识别节名
        if (line[0] == '[') {
            char *end = strchr(line, ']');
            if (end) {
                *end = '\0';
                strncpy(current_section, line + 1, MAX_SECTION - 1);
                printf("[节] %s\n", current_section);
            }
            continue;
        }

        // 提取键值对
        char *eq = strchr(line, '=');
        if (eq) {
            *eq = '\0';
            char *key = line;
            char *value = eq + 1;
            trim(key);
            trim(value);
            printf("  %s = %s\n", key, value);
        }
    }

    fclose(fp);
    return 0;
}

int main() {
    parse_ini("config.ini");
    return 0;
}

小技巧:我习惯用fgets而不是fscanf来读行。因为fgets能完整保留一行内容,方便你后续做各种判断。用fscanf的话,遇到空格就断了,容易出问题。

14.4 避坑指南

我曾经踩过几个坑,今天一并告诉你:

  • 注释符号不止一种——有的文件用;,有的用#。我建议两种都支持。
  • 值里可能包含等号——比如password = abc=123。这时候你只能按第一个等号切分。
  • 节名不能重复——如果同一个节出现两次,后面的会覆盖前面的。我一般会在解析时做个检查。
  • 文件编码问题——Windows下保存的ini文件可能是ANSI编码,Linux下是UTF-8。跨平台时要注意。

警告:千万不要用sscanf(line, "%s = %s", key, value)来解析。因为%s遇到空格就停了,而值里很可能包含空格。老老实实用strchr找等号,然后手动切分。

14.5 知识体系总览

下面这张图,帮你把整个配置文件解析的流程串起来。你看一遍就能记住。

配置文件解析流程 打开文件 逐行读取 判断行类型 注释/空行 → 跳过 [节名] → 记录节 key=value → 提取 解析完成后,将数据存入结构体或哈希表,供程序其他模块使用

14.6 进阶:支持多值类型

实际项目中,配置值不全是字符串。比如端口号是整数,超时时间是浮点数。我建议你在解析完字符串后,再根据需求做一次类型转换。

举个例子:

// 假设你已经解析出 value 字符串
int port = atoi(value);           // 转整数
double timeout = atof(value);     // 转浮点数
// 字符串就直接用,不用转了

我的习惯:我会在解析器里加一个回调函数机制。每解析到一个键值对,就调用用户注册的回调函数,让用户自己决定怎么处理这个值。这样解析器本身很通用,业务逻辑全在回调里。

14.7 总结

配置文件解析,说白了就是“读一行,判一行,拆一行”。

你只要记住三点:跳过干扰项、识别结构、提取数据。再加上对空白的处理,基本就能应付90%的场景。

我给你的代码可以直接跑起来试试。找个ini文件,改几行,看看解析结果对不对。动手练一遍,比看十遍都管用。


公众号:蓝海资料掘金营,微信deep3321