在 C 中解析 INI 文件 - 如何存储部分及其键和值?

Parsing INI file in C - how to store sections and its' keys and values?

我正在尝试仅使用 C 中的标准库来解析 .ini 文件。 输入文件如下所示:

[section1]
key1 = value1 
key2 = value2

[section2]
key3 = vaule3
key4 = value4
key5 = value5
...

im 运行 与 ./file inputfile.ini section2.key3 我想从 section2

获取 key3 的值

我的问题是:如何轻松地存储键和值? - 我是一个初学者,所以我需要一些简单易行的东西 - 也许是结构,但如果我不知道键的数量,如何将所有键和值存储在结构中?

我卡在这里了,两个字符串部分和 current_section 看起来一样,但在 if(section == current_section) 中它们没有通过 True,问题是什么?

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


int main(int argc, char *argv[]) {

    FILE * fPointer;
    fPointer = fopen(argv[1], "r");  // read from file

    char singleLine[30];
    char section[30];
    char key[30];
    int right_section = 0;
    char current_section[30];

    sscanf(argv[2], "%[a-zA-Z0-9].%[a-zA-Z0-9]", section, key); //split of section.key

    while(!feof(fPointer)){
        fgets(singleLine, 30, fPointer); //read line by line
        printf("%s", singleLine);
        char current_key[30];
        char current_value[30];

        if(singleLine[0]=='['){
            sscanf(singleLine, "[%127[^]]", current_section); //strip from []
            printf("current section:%s%s", current_section, section); //both look equally
            if(current_section == section){ // doesn't work here, current_section == section looks the same but if doesnt work
                right_section = 1;
                printf("yes, right");
            }
        }

    }

    fclose(fPointer);

    return 0;
}```

正如您所指出的,问题出在字符串比较 if 语句中,问题在于 char 数组(以及 C 中的任何数组)变量实际上是指向第一个元素的指针数组(这个解释过于简单化)。

因此,在您的 if 语句中,您实际上是在比较两个数组变量中每一个的第一个元素的内存地址,而不是相互比较内容。

要正确比较字符串,有多种选择,您可以手动进行(遍历每个数组并逐个元素比较),或者您可以使用标准库中的一些帮助程序,例如 strcmpmemcmp.

例如,您可以如下重写 if 语句:

#include <string.h>

if (memcmp ( section, current_section, sizeof(section) ) == 0) {
   // both arrays have the same content
}

您正在沿着正确的道路前进,但如果您想确保事情正常进行,则必须以不同的方式处理一些事情。如果您从这个答案中没有得到任何其他信息,请了解如果没有 检查 return(适用几乎每个你使用的函数,除非后面的代码操作不依赖于结果——比如只打印值)另外,你从不使用 while (!feof(fpointer)),例如参见:Why is while ( !feof (file) ) always wrong?

现在,如何解决这个问题。首先,如果你的数组大小需要一个常量,那么 #define 常量或使用全局 enum。例如,对于我的 sectinisectkeyinikeyval 缓冲区,我将定义 SPLTC 然后为我的行缓冲区,我定义 MAXC,例如

#define SPLTC 128       /* if you need a constant, #define one (or more) */
#define MAXC  256

根据您是否需要 -ansi 或 c89/90 兼容,在任何操作之前声明您的变量,例如

int main (int argc, char **argv) {

    char buf[MAXC], sect[SPLTC], inisect[SPLTC], key[SPLTC], inikey[SPLTC], val[SPLTC];
    FILE *fp = NULL;

然后您要做的第一件事是验证是否在命令行上提供了足够的参数:

    if (argc < 3) { /* validate 2 arguments provided */
        fprintf (stderr,
                "error: insufficient number of arguments\n"
                "usage: %s file.ini section.key\n", argv[0]);
        return 1;
    }

接下来您将打开您的文件并验证它是开放阅读的:

    /* open/validate file open for reading */
    if ((fp = fopen (argv[1], "r")) == NULL) {
        fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
        perror ("fopen");
        return 1;
    }

然后将您的 section.key 参数 argv[2] 拆分为 sectkey 以及 validate分离:

    /* split section.key into sect & key */
    if (sscanf (argv[2], " %127[^.]. %127s", sect, key) != 2) {
        fputs ("error: invalid section.key\n", stderr);
        return 1;
    }

现在进入您的读取循环以在文件中找到您的部分(您始终使用读取函数本身的 return 控制循环):

    while (fgets (buf, MAXC, fp)) {                 /* read each line */
        if (buf[0] == '[') {                        /* is first char '[]'? */
            if (sscanf (buf, " [%127[^]]", inisect) == 1) { /* parse section */
                if (strcmp (sect, inisect) == 0)    /* does it match 2nd arg? */
                    break;                          /* if so break loop */
            }
        }
    }

如何检查是否找到了该部分?您可以保留标志变量,就像您对 right_section 所做的那样,或者...想想如果找不到您的部分,您会在文件中的什么位置?你会在 EOF。所以现在你可以正确检查feof(fp),例如

    if (feof (fp)) {    /* if file stream at EOF, section not found */
        fprintf (stderr, "error: EOF encountered before section '%s' found.\n", 
                sect);
        return 1;
    }

如果你没有因为找不到你的部分而退出(意味着你在代码中到了这一步),只需阅读每一行 validating 分离为 inikeyval(如果验证失败——你已经阅读了该部分中所有没有匹配的 key/val 对)如果你在期间找到关键匹配您阅读了 成功 部分,您有 inikeyval。如果你在没有匹配的情况下完成循环,你可以检查你是否发出错误,如果你到达 EOF 没有匹配,你可以在循环后再次检查 feof(fp),例如

    while (fgets (buf, MAXC, fp)) {                 /* continue reading lines */
        /* parse key & val from line */
        if (sscanf (buf, " %127s = %127s", inikey, val) != 2) { /* if not key & val */
            fprintf (stderr, "error: end of section '%s' reached "
                    "with no matching key found.\n", sect);
            return 1;
        }
        if (strcmp (key, inikey) == 0) {           /* does key match? */
            printf ("section : %s\n  key : %s\n  val : %s\n", sect, key, val);
            break;
        }
    }

    if (feof (fp)) {    /* if file stream at EOF, key not found */
        fprintf (stderr, "error: EOF encountered before key '%s' found.\n", 
                argv[3]);
        return 1;
    }

基本上就是这样。如果你把它放在一起你有:

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

#define SPLTC 128       /* if you need a constant, #define one (or more) */
#define MAXC  256

int main (int argc, char **argv) {

    char buf[MAXC], sect[SPLTC], inisect[SPLTC], key[SPLTC], inikey[SPLTC], val[SPLTC];
    FILE *fp = NULL;

    if (argc < 3) { /* validate 2 arguments provided */
        fprintf (stderr,
                "error: insufficient number of arguments\n"
                "usage: %s file.ini section.key\n", argv[0]);
        return 1;
    }

    /* open/validate file open for reading */
    if ((fp = fopen (argv[1], "r")) == NULL) {
        fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
        perror ("fopen");
        return 1;
    }

    /* split section.key into sect & key */
    if (sscanf (argv[2], " %127[^.]. %127s", sect, key) != 2) {
        fputs ("error: invalid section.key\n", stderr);
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {                 /* read each line */
        if (buf[0] == '[') {                        /* is first char '[]'? */
            if (sscanf (buf, " [%127[^]]", inisect) == 1) { /* parse section */
                if (strcmp (sect, inisect) == 0)    /* does it match 2nd arg? */
                    break;                          /* if so break loop */
            }
        }
    }

    if (feof (fp)) {    /* if file stream at EOF, section not found */
        fprintf (stderr, "error: EOF encountered before section '%s' found.\n", 
                sect);
        return 1;
    }

    while (fgets (buf, MAXC, fp)) {                 /* continue reading lines */
        /* parse key & val from line */
        if (sscanf (buf, " %127s = %127s", inikey, val) != 2) { /* if not key & val */
            fprintf (stderr, "error: end of section '%s' reached "
                    "with no matching key found.\n", sect);
            return 1;
        }
        if (strcmp (key, inikey) == 0) {           /* does key match? */
            printf ("section : %s\n  key : %s\n  val : %s\n", sect, key, val);
            break;
        }
    }

    if (feof (fp)) {    /* if file stream at EOF, key not found */
        fprintf (stderr, "error: EOF encountered before key '%s' found.\n", 
                argv[3]);
        return 1;
    }
}

例子Use/Output

找到有效的 section/key 组合:

$ ./bin/readini dat/test.ini section2.key3
section : section2
  key : key3
  val : vaule3

$ /bin/readini dat/test.ini section2.key5
section : section2
  key : key5
  val : value5

$ ./bin/readini dat/test.ini section1.key2
section : section1
  key : key2
  val : value2

尝试找到无效的 section/key 组合。

$ ./bin/readini dat/test.ini section1.key3
error: end of section 'section1' reached with no matching key found.

$ ./bin/readini dat/test.ini section2.key8
error: EOF encountered before key 'key8' found.

检查一下,如果您还有其他问题,请告诉我。