File based configuration handling in C (Unix)

2020-02-07 18:44发布

问题:

This is probably one of the most common tasks / problems when programming; You need to store the configuration of your application somewhere.

While I'm trying to create a webserver or other applications, I'd like to keep the code as clean as possible since my main interest in programming is architecture. This results in me wanting to store configurations in a file which can be changed without having to re-compile the software.

I'm not here to re-invent the wheel or anything like that, so what I'd like to do is creating a Configuration reader in C on *nix. The configuration might look a lot like any other software's configuration; Apache, vsftpd, MySQL, etc.

The basic question is: How do you read from a textfile and process each line efficiently (in pure C)? Do I need to use fgetc() and process each char?

回答1:

Okay, so let's hit the other part. You need to think about what you'd like to have as your "language". In the UNIX world, the sort of canonical version is probably whitespace-delimited text (think /etc/hosts) or ":" delimited text (like /etc/passwd).

You have a couple of options, the simplest in some sense being to use scanf(3). Again, read the man page for details, but if a line entry is something like

port    100

then you'll be looking for something like

char inbuf[MAXLINE];
int  val;

scanf("%s %d\n", &inbuf[0], &val);

You can get a bit more flexibility if you write a simple FSA parse: read characters one at a time from the line, and use a finite automaton to define what to do.



回答2:

Hmmm there is LibConfig.

I have found it in Wiki



回答3:

Various people have given reasonably good advice - the Pure-FTP example is interesting.

You should also read TAOUP (The Art of Unix Programming) by E S Raymond. It has examples a-plenty of configuration files. It also outlines canonical idioms for the configuration files. For example, you should probably allow '#' to start a comment to the end of the line, and ignore blank lines. You should also decide what you will do if the configuration file contains a line you don't understand - whether to ignore and continue silently, or whether to complain. (I prefer things that complain; then I know why what I've just added isn't having any effect - whereas silent ignoring means I don't know whether the entry I just added has any effect.)

Another problem is locating the configuration file. Do you do that by compiled-in location, by a default install location with environment variable to override, or by some other piece of magic? Make sure there's a command line option to allow the configuration file to be specified absolutely - even consider making that the only way to do it.

Otherwise, within broad limits, keep it simple and everyone will be happier.



回答4:

Okay, here's a real example of C code:

/* demo-fgets -- read a "demo.text", copy to stdout with line
   numbers. */

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

#define MAXLINE 100

FILE * fp;
char bufr[MAXLINE];

extern int errno ;

int main(int argc, char ** argv){
    int count = 0 ;
    if((fp = fopen("demo.text","r")) != NULL){
        /* then file opened successfully. */
        while(fgets(bufr,MAXLINE,fp)!=NULL){
            /* then no read error */
            count +=1;
            printf("%d: %s",     /* no "\n", why? */
                   count, bufr);
        }
        /* fgets returned null */
        if(errno != 0){
            perror(argv[0]);    /* argv[0]?  Why that? */
            exit(1);
        }
        exit(0);                /* EOF found, normal exit */
    } else {                    /* there was an error on open */
        perror(argv[0]);
        exit(1);
    }
}

I run it with this input file:

520 $ cat demo.text 
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum
aliquet augue id quam. Sed a metus. Quisque sit amet quam. Sed id
ante. In egestas est non mi. Sed vel velit non elit vehicula
viverra. Curabitur eget tortor in ipsum vulputate
faucibus. Suspendisse imperdiet mauris at nibh. Sed interdum. Maecenas
vulputate, massa vel placerat mattis, ante est tincidunt sem, in
sollicitudin velit lacus non tortor. Etiam sagittis consequat nisl. 

Vestibulum id leo quis mauris gravida placerat. Donec aliquet justo a
tortor. Etiam nisi nibh, auctor non, luctus et, aliquam vitae,
metus. Cum sociis natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Nunc lacinia quam a ligula. Nulla quis nisi eu
nunc imperdiet cursus. Nunc vitae nisi vitae tellus posuere
sollicitudin. Nunc suscipit, dui ac interdum euismod, pede nisl varius
dui, sed mattis libero mauris eu felis. Nam mattis dui eget
nunc. Suspendisse malesuada, pede eget posuere pellentesque, neque
eros pretium nibh, ut blandit dui leo dapibus orci. Etiam lacinia
lectus at orci. Donec ligula lacus, sagittis nec, sodales et,
fringilla lobortis, eros. Etiam sit amet nulla. Aliquam mollis pede id
enim. Etiam ligula felis, pulvinar nec, vestibulum molestie, interdum
ut, urna. Ut porta ullamcorper diam. Nullam interdum arcu. 

Pellentesque habitant morbi tristique senectus et netus et malesuada
fames ac turpis egestas. Etiam eu enim quis sem accumsan
tristique. Proin non sem. Etiam quis ante. Aenean ornare pellentesque
dolor. Praesent sodales. Cras dui velit, scelerisque a, accumsan a,
vestibulum in, dui. Pellentesque sed sapien. Etiam augue est,
convallis eget, egestas vel, molestie id, turpis. Cum sociis natoque
penatibus et magnis dis parturient montes, nascetur ridiculus
mus. Cras posuere lorem eu diam. Ut ultricies velit. Nunc imperdiet
suscipit mauris. Vestibulum molestie elit id risus. Phasellus et
purus. Vestibulum id mauris. Fusce gravida elit quis turpis. Aliquam
ut est. 

Sed in mauris eu nulla rhoncus suscipit. Nam id dolor sit amet turpis
placerat sodales. Nunc ipsum. Quisque diam tellus, dapibus non,
interdum at, aliquam sit amet, tellus. Donec non pede eget massa
aliquam semper. Quisque dictum lacinia ipsum. Fusce magna purus,
mattis id, commodo et, lobortis eu, arcu. Vestibulum viverra neque a
nulla. Cum sociis natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Pellentesque vel felis in ligula blandit
auctor. Quisque quam. Curabitur turpis. Morbi molestie augue a
nisi. Nulla sollicitudin sagittis elit. Suspendisse in odio sed magna
dictum vestibulum. Duis facilisis lorem eget neque. Proin sit amet
urna eget velit scelerisque aliquam. Pellentesque imperdiet. Nullam
sapien. Nullam placerat ipsum eget metus. 

Mauris ornare risus eu velit. Morbi bibendum diam in sem. Morbi
aliquet nisl sit amet quam. Donec ornare sagittis nibh. Fusce ac
lectus. Sed sit amet risus. Integer facilisis commodo
sem. Pellentesque facilisis. Donec libero. Lorem ipsum dolor sit amet,
consectetur adipiscing elit.


回答5:

There are a number of ways. You don't need to use fgetc. You should probably read the stdio man page, but the canonical thing would be to open the file with fopen(3), then read using fgets(3) to read a line at a time. That would look something like:

#include <stdio.h>

FILE * fp ;
char bufr[MAXLINE];

if((fp = fopen(filename, "r") != NULL){
    while(! feof(fp)){
         fgets(bufr, MAXLINE, fp);
         /* Do stuff */
    }
} else {
    /* error processing, couldn't open file */
}

You could also look at libini at Sourceforge.



回答6:

Why would you ever write this code from scratch, it's been done so many times; just find a good F/OSS implementation and use that.

How do you read from a Textfile and process each line efficiently

Don't worry about efficiency, it doesn't matter for reading configuration files. Go for simplicity and maintainability instead.



回答7:

The way I would have done it (pseudo-code):

while(there is input) {
  get one line;
  if (it is empty line || it beings with spaces followed by a '#') {
    skip this line, either empty or it's a comment;
  }
  find the position of the token that splits option name and its value;
  copy the option name and its value to separate variables;
  removing spaces before and after these variables if necessary;
  if (option == option1) {
     parse value for option 1;
  } else if (option == option2) {
     parse value for option 2;
  } else {
     handle unknown option name;
  }
  check consistency of options if necessary;
}

Note that part of the code (checking for empty lines, comments, and spaces around variables) is there to give you extra flexibility when writing the config file. For example, you won't crash your program by accidentally leaving an extra space here and there.

This is assuming you have config files that look like the following:

# comment for option 1
option1 = value1

# comment for option 2
option2 = value2

...


回答8:

Yet another solution - Pure-ftpd

Unlike many daemons, Pure-FTPd doesn't read any configuration file. Instead, it uses command-line options. ... Adding a parser for configuration files in the server is a bad idea. It slows down everything and needs resources for nothing.

And for options there is getopt



回答9:

There is also getline() function and friends in GNU LibC



回答10:

I wrote a clean, dependency-less, event-driven INI file parser in C a while ago. I've decided to throw it into a public Hg repository. It is MIT-licenced, so you're free to use it any where you like.



回答11:

Have you consider of storing config values as environment variables? :) And config file will be a shell script you run before your program. (actually shell script will execute it to save variables). That way you are using shell as config parser :)

Use http://www.google.com/codesearch and "read config"

See Example for httpd



回答12:

C is not easy as Python, yet we still want something like "import"...

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*conf*/
/*
 * #ip
 * ip:192.168.0.0.1
 * #port
 * port:8888
 * */
/*read conf*/
void test()
{
        FILE *fp = fopen("conf", "r");
        if (fp == NULL)
        {
                return;
        }

        char line[1024] = { 0 };
        while (!feof(fp))
        {
                memset(line, 0, 1024);
                fgets(line, 1024, fp);
                if (line[0] == '#')
                {
                        continue;
                }

                int len = strlen(line);
                char *pos = strchr(line, ':');
                if (pos == NULL)
                {
                        continue;
                }
                char key[64] = { 0 };
                char val[64] = { 0 };

                int offset = 1;
                if (line[len - 1] == '\n')
                {
                        offset = 2;
                }

                strncpy(key, line, pos - line);
                strncpy(val, pos + 1, line + len - offset - pos);

                printf("%s -> %s\n", key, val);
        }
}

int main()
{
        printf("read start...\n");
        test();
        printf("read success!!!\n");
        int c = getchar();
        return 0;
}

The above based on conf formated in x:y...

ip:192.168.0.0.1
port:8888