欢迎访问我的网站,希望内容对您有用,感兴趣的可以加入我们的社群。

C语言读写ini配置文件

C/C++ 迷途小书童 2年前 (2023-06-15) 993次浏览 0个评论

环境

  • windows 10 64bit
  • Clion 2023.1

ini简介

ini 文件格式是一种用于保存配置信息的简单文本格式。它通常由多个节(section)组成,每个节包含多个键值对(key-value pair)。

下面是 ini 文件的基本语法规则

  • 一个ini文件由多个节组成,每个节用方括号([])括起来
  • 节后面可以跟一个或多个键值对,每个键值对占一行,格式为key=value
  • 键值对中,键(key)和值(value)之间用等号(=)分隔,键和值之间可以有空格。键和值都是字符串,可以包含任何字符,但是如果键或值中包含等号(=)或分号(;)这样的特殊字符,需要进行转义
  • 注释以井号(#)或分号(;)开头,注释可以出现在任何位置,包括行首、行尾或键值对之间

下面是一个 ini 示例文件,后面代码也会去解析它

# 服务器配置
[server]
ip = 192.168.1.100
port = 8080

;数据库配置
[database]
host = localhost
port = 3306
username = username
password = password

在这个示例文件中,有两个节:serverdatabaseserver 包含2个键值对:ipport,而 database 包含4个键值对,hostportusernamepassword。最后,还有2行注释,一行以 # 开头,另一行以 ; 开头。

代码示例

这里使用 Clion 集成开发环境,编译器使用自带的 MinGW,首先创建一个 C 可执行项目,命名为 Demo

创建完成后,添加一个头文件 ini_parser.h,内容如下

#ifndef DEMO_INI_PARSER_H
#define DEMO_INI_PARSER_H

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

// 获取key对应的值
int GetIniKeyString(char *section,char *key,char *filename,char *buf);

// 修改key对应的值
int PutIniKeyString(char *section,char *key,char *val,char *filename);

#endif //DEMO_INI_PARSER_H

接着新建一个 C 源码文件 ini_parser.c,内容如下

#include <stdio.h>
#include <string.h>
#include "errno.h"

/*
* 参数:
* section:  配置文件中的节sectin
* key:      配置项的标识
* filename: ini配置文件路径
*
* 返回值:    找到需要的值返回结果0,否则返回-1
*/
int GetIniKeyString(char *section, char *key, char *filename, char *buf)
{
    FILE *fp;

    // 用来标记是否找到section
    int flag = 0;
    char sSection[64], *wTmp;
    char sLine[1024];

    // 节section字符串
    sprintf(sSection, "[%s]", section);

    if (NULL == (fp = fopen(filename, "r")))
    {
        printf("open %s failed.\n", filename);
        return -1;
    }

    // 读取ini中的每一行
    while (NULL != fgets(sLine, 1024, fp))
    {
        // 处理ini文件中的注释行
        if ('#' == sLine[0])
            continue;

        if (';' == sLine[0])
            continue;

        // 定位=的位置
        wTmp = strchr(sLine, '=');
        if ((NULL != wTmp) && (1 == flag))
        {
            if (0 == strncmp(key, sLine, strlen(key)))
            {
                sLine[strlen(sLine) - 1] = '\0';

                while (*(wTmp + 1) == ' ')
                {
                    wTmp++;
                }

                // 获取key对应的value
                strcpy(buf, wTmp + 1);

                fclose(fp);
                return 0;
            }
        }
        else
        {
            if (0 == strncmp(sSection, sLine, strlen(sSection)))
            {
                // 不存在键值对的情况下,标记flag
                flag = 1;
            }
        }
    }

    fclose(fp);
    return -1;
}

/*
* 参数:
* section:  配置文件中的节sectin
* key:      配置项的标识
* val:      配置项标识对应的值
* filename: ini配置文件路径
*
* 返回值:    成功返回结果0,否则返回-1
*/
int PutIniKeyString(char *section, char *key, char *val, char *filename)
{
    FILE *fpr, *fpw;
    int flag = 0;
    int ret;
    char sLine[1024], sSection[32], *wTmp;

    sprintf(sSection, "[%s]", section);

    if (NULL == (fpr = fopen(filename, "r")))
        return -1;

    // 临时文件名
    sprintf(sLine, "%s.tmp", filename);

    fpw = fopen(sLine, "w");
    if (NULL == fpw)
        return -1;

    while (NULL != fgets(sLine, 1024, fpr))
    {
        if (2 != flag)
        {
            wTmp = strchr(sLine, '=');
            if ((NULL != wTmp) && (1 == flag))
            {
                if (0 == strncmp(key, sLine, strlen(key)))
                {
                    // 找到对应的key
                    flag = 2;
                    sprintf(wTmp + 1, " %s\n", val);
                }
            }
            else
            {
                if (0 == strncmp(sSection, sLine, strlen(sSection)))
                {
                    // 找到section的位置
                    flag = 1;
                }
            }
        }

        // 写入临时文件
        fputs(sLine, fpw);
    }

    fclose(fpr);
    fclose(fpw);

    sprintf(sLine, "%s.tmp", filename);

    // rename函数在windows上和linux上表现有差异,看文章中的备注
    ret = rename(sLine, filename);
    if (ret != 0)
    {
        if (errno == EEXIST)
        {
            // 如果目标文件已经存在,需要先删除,再重命名
            if (remove(filename) == 0)
            {
                if (rename(sLine, filename) == 0)
                {
                    // printf("File %s has been renamed to %s\n", sLine, filename);
                    return 0;
                }
            }
        }
    }

    return ret;
}

最后编辑工程入口文件 main.c

#include <stdio.h>
#include "ini_parser.h"

int main(int argc, char const *argv[]) {
    char buff[128];
    int ret;

    ret = GetIniKeyString("server", "ip", "config.ini", buff);
    printf("get ret:%d,value: %s\n", ret, buff);

    memset(buff, 0, sizeof(buff));
    ret = GetIniKeyString("database", "db", "config.ini", buff);
    printf("get ret:%d, value: %s\n", ret, buff);

    ret = PutIniKeyString("server", "port", "80", "config.ini");
    printf("put ret:%d\n", ret);

    memset(buff, 0, sizeof(buff));
    ret = GetIniKeyString("server", "port", "config.ini", buff);
    printf("get ret:%d, value: %s\n", ret, buff);
    return 0;
}

使用 IDE 的好处就是,在添加各个文件时,它会帮你修改 CMakeLists.txt,不需要自己手动去添加

cmake_minimum_required(VERSION 3.25)
project(Demo C)

set(CMAKE_C_STANDARD 99)

add_executable(Demo main.c ini_parser.h ini_parser.c)

然后,就可以编译整个工程了,选中 IDE 顶部菜单栏中的 Build –> Build Project,完成后,会在目录 cmake-build-debug 下生成可执行文件 Demo.exe,在运行之前,还需要将配置 config.ini(也就是上文提到的ini示例文件) 也放到 cmake-build-debug

最后,按下快捷键 Shift+F10 来运行一下

ini in c lanuage

备注

rename 方法在 windowslinux 上的表现不一样,如果目标文件存在,linux 可以直接覆盖。但是,在 windows 上,返回值就是-1,errno 提示 File Exists

参考资料

喜欢 (0)

您必须 登录 才能发表评论!