0

I'm trying to parse .ini file using only STANDARD Libraries in C. Input files look like:

[section1]
key1 = value1 
key2 = value2

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

im running that with ./file inputfile.ini section2.key3 and i want to get value of key3 from section2

MY QUESTION is: How to easily store keys and values? - im a total beginner, so I need something simple and easy to implement - maybe struct but how to store all keys and values inside struct if i don't know quantity of keys?

I got stuck here, two strings section and current_section looks equally but in if(section == current_section) they don't pass True, what is the problem?

#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;
}```
  • 1
    Does this answer your question? [INI file parser for C](https://stackoverflow.com/questions/20565229/ini-file-parser-for-c) – Lion King Apr 04 '20 at 00:25
  • 1
    Well, just read each line with `fgets()` into a sufficiently sized character array (don't skimp on size), say `buf`, then check if `buf[0] == '['` (or just `*buf == '['`), if so read the section name into another array with `sscanf()`, if not your section, keep reading until the next `'['` until you find your section, Then read with `fgets()` using `sscanf()` to separate into `key` and `value` strings until you match your search criteria. – David C. Rankin Apr 04 '20 at 00:26
  • @DavidC.Rankin thanks man I didn't think of that this way. Can you tell me how to store each section and its keys and values? What should I use for that, how would that look like? I know that I dont need to store them for my problem, but I would like to know – andrew_the_coder Apr 04 '20 at 00:36
  • 1
    Well, first it would make more sense to take your input as `./file inputfile.ini section2 key3` and save having to split `section2.key3`. Then I would just declare `char buf[256], sect[128], key[128], value[128];` open your file and validate it is open for reading. Then `while (fgets (buf, sizeof buf, fp)) { ...` to read each line testing `if (buf[0] == '[') { ...` to find each section start. Then you can read the section with `if (sscanf (buf, " [%127[^]]", sect) == 1)` and test `if (strcmp (sect, argv[2]) == 0)` to see if you match your section. Then use `sscanf` to split key/values. – David C. Rankin Apr 04 '20 at 00:46
  • If you get stuck, edit your question and add what you have so far to the end of your question and I'm happy to help further. For a simple approach You can loop looking for your section, `break` the loop when you have filled the `sect` variable with the correct section. Now you can check `if (feof(fp)) { printf ("error: end of section '%s' reached with no matching key found.\n", sect); return 1; )` Then you can enter your second loop to match the `key` and `val`. Your `sscanf()` format string to parse `key` and `val` will be something like `" %127s = %127s"`. – David C. Rankin Apr 04 '20 at 01:19
  • ok, I got stuck – andrew_the_coder Apr 04 '20 at 02:17
  • Oh -- yes you did, You will want to look at [**Why is while ( !feof (file) ) always wrong?**](https://stackoverflow.com/questions/5431941/why-is-while-feoffile-always-wrong). Now things look okay to that point and you have your `section`, but you must continue reading and splitting lines into `key` and `value` to match the `key`, e.g. `while (fgets (singleline, 30, fpointer)) { if (sscanf (singleline, " %29s = %29s", inikey, val) != 2) { /* error key not found in section */ return 1;} if (strcmp (key, inikey) == 0) { /* you have inikey and val! */ }` – David C. Rankin Apr 04 '20 at 02:30
  • Also note -- you are somewhat skimping on buffer size... I could see a section, key or value being more than 29-chars. Pick your longest anticipated key or value or section and then double the number of characters -- *at minimum*. (personally I would read each line into a minimum of 256-char array as the buffer and for each section, key and value into a 128-char array as the buffer) If you are on an embedded system where memory space is at a premium, then 30 is fine -- as long as you know nothing exceeds 29-chars... – David C. Rankin Apr 04 '20 at 02:42
  • The problem is you cannot use any user-input or parsing function correctly unless you ***check the return***. You have `sscanf(singleLine, "[%127[^]]", current_section);` You need `if (sscanf(singleLine, " [%29[^]]", current_section) == 1) { /* then test current_section */ }` **note** the extra **space** before `" [%29[^]]"` and note the *field-width* modifier is one less than your buffer size to save room for `'\0'`. **Lesson** -- ***validate, validate, validate*** – David C. Rankin Apr 04 '20 at 02:51

2 Answers2

0

As you pointed out, the issue is in the strings compare if statement, the thing is that a char array (and any array in C) variable in reality is a pointer to the first element of the array (this explanation is over-simplified).

So in your if statement you are really comparing the memory addresses of the first element of each of the two array variables instead of comparing the content of each other.

For doing a correct compare of strings there are several options, you could do it manually (iterating over each array and comparing element by element) or you could use some helpers from the standar library, like strcmp or memcmp.

For example you could re-write your if statement as below:

#include <string.h>

if (memcmp ( section, current_section, sizeof(section) ) == 0) {
   // both arrays have the same content
}
Lucas S.
  • 13,391
  • 8
  • 46
  • 46
0

You are working down the correct path, but there are a few things that you must approach differently if you want ensure things work correctly. If you take nothing else from this answer, learn that you cannot use any input or parsing function without checking the return (that applies to virtually every function you use, unless the operation of code that follows does not depend on the result -- like just printing values) Also, you never use while (!feof(fpointer)), e.g. see: Why is while ( !feof (file) ) always wrong?

Now, how to approach the problem. First, if you need a constant for your array size, then #define a constant or use a global enum. For example, for my sect, inisect, key, inikey and val buffers I would define SPLTC and then for my line buffer, I define MAXC, e.g.

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

Depending on whether you need to be -ansi or c89/90 compatible, declare your variables before any operations, e.g.

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

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

Then the first thing you will do is validate that sufficient arguments were provided on the command line:

    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;
    }

Next you will open your file and validate that it is open for reading:

    /* 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;
    }

Then split your section.key argument argv[2] into sect and key and validate the separation:

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

Now enter your read loop to find your section in the file (you always control the loop with the return of the read function itself):

    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 */
            }
        }
    }

How do you check that the section was found? You can keep a flag-variable as you have done with right_section, or... think about where you would be in the file if your section wasn't found? You would be at EOF. So now you can correctly check feof(fp), e.g.

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

If you haven't exited due to not finding your section (meaning you got to this point in the code), just read each line validating a separation into inikey and val (if the validation fails -- you have read all the key/val pairs in that section without a match) If you find the key match during your read of the section success you have your inikey and val. If you complete the loop without a match you can check if you issue an error, and if you reach EOF without a match, you can again check feof(fp) after the loop, e.g.

    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;
    }

That's basically it. If you put it altogether you have:

#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;
    }
}

Example Use/Output

Finding valid section/key combinations:

$ ./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

Attempts to find invalid section/key combinations.

$ ./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.

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85