Обработка конфигурации на основе файлов в C (Unix)

Вероятно, это одна из самых распространенных задач / проблем при программировании; Вам нужно где-то сохранить конфигурацию вашего приложения.

Пока я пытаюсь создать веб-сервер или другие приложения, я хотел бы, чтобы код был как можно более чистым, поскольку мой главный интерес в программировании - это архитектура. Это приводит к тому, что я хочу хранить конфигурации в файле, который можно изменить без повторной компиляции программного обеспечения.

Я здесь не для того, чтобы заново изобретать колесо или что-то в этом роде, поэтому я хотел бы создать средство чтения конфигурации на C на * nix. Конфигурация может быть очень похожа на конфигурацию любого другого программного обеспечения; Apache, vsftpd, MySQL и т. Д.

Основной вопрос: как вы читаете из текстового файла и эффективно обрабатываете каждую строку (на чистом C)? Нужно ли мне использовать fgetc() и обрабатывать каждый символ?


person Filip Ekberg    schedule 04.01.2009    source источник
comment
XML. Ик. Это ОЧЕНЬ накладные расходы для простого ini-файла.   -  person Charlie Martin    schedule 05.01.2009
comment
Действительно моя мысль. И он должен быть чистым C и не использовать никаких внешних библиотек. Итак, я хочу узнать, как это сделать :)   -  person Filip Ekberg    schedule 05.01.2009


Ответы (12)


Хорошо, давайте перейдем к другой части. Вам нужно подумать о том, что вы хотите использовать в качестве своего «языка». В мире UNIX своего рода каноническая версия, вероятно, представляет собой текст с разделителями-пробелами (подумайте /etc/hosts) или текст с разделителями ":" (например, /etc/passwd).

У вас есть несколько вариантов, самый простой в некотором смысле - использовать scanf (3). Опять же, прочтите страницу руководства для получения подробной информации, но если запись в строке похожа на

port    100

тогда вы будете искать что-то вроде

char inbuf[MAXLINE];
int  val;

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

Вы можете получить немного больше гибкости, если напишете простой анализ FSA: считайте символы по одному из строки и используйте конечный автомат, чтобы определить, что делать.

person Charlie Martin    schedule 05.01.2009
comment
Спасибо, это и другие ваши сообщения касаются того, что я хочу! - person Filip Ekberg; 05.01.2009
comment
Это уязвимо для переполнения буфера, если поле длиннее MAXLINE, верно? - person Flash; 24.05.2014
comment
Ага. И? В конце концов, это обсуждение создания файла конфигурации. - person Charlie Martin; 25.05.2014
comment
@CharlieMartin Ага. И? В конце концов, это обсуждение создания файла конфигурации. ааа вот и твой подвиг. Почти все файлы конфигурации по определению доступны внешнему миру, и они могут быть изменены любым способом пользователями и другими сторонними программами. Так что, в конце концов, это не просто обсуждение создания файла конфигурации. См. stackoverflow.com/questions/1621394/, чтобы узнать, как правильно использовать scanf. - person Bora M. Alper; 23.06.2017
comment
@boramalper Итак, да, вы правы, я не говорил об уязвимостях переполнения буфера, когда объяснял новому программисту, как читать простой текстовый файл. Я тоже не объяснял endianness. Или ошибки сегментации. Я часто замечал, что одним из моих преимуществ обучения программированию является то, что я все еще помню, когда я не знал всего. - person Charlie Martin; 25.06.2017
comment
@CharlieMartin Я не знаю, что заставило вас подумать, что автор - новый программист, поскольку я не вижу никаких указаний на это в вопросе, но, тем не менее, почему вообще учат неправильно? Помимо порядка байтов (при условии, что мы не перемещаем файлы конфигурации), переполнение буфера и сбои сегментации также являются ошибкой прямо в программном обеспечении, а не просто проблемой безопасности, о которой вы должны беспокоиться только позже. Что, если путь в конфигурации длиннее MAXLINE, учитывая, что он также будет иметь отступ с табуляцией (или, что еще хуже, пробелами). - person Bora M. Alper; 25.06.2017
comment
@boramalper часть, где он спрашивает, нужно ли ему использовать fgetc или что-то еще. - person Charlie Martin; 25.06.2017

Хммм, есть LibConfig.

Я нашел его в Wiki

person Malx    schedule 05.01.2009
comment
Что ж, это очень хорошая библиотека, которую я, вероятно, использовал бы, если бы не хотел узнавать о базовой структуре. - person Filip Ekberg; 05.01.2009

Разные люди давали достаточно хорошие советы - интересен пример Pure-FTP.

Вам также следует прочитать TAOUP (Искусство программирования Unix) Э. С. Реймонда. Он содержит множество примеров конфигурационных файлов. Он также описывает канонические идиомы для файлов конфигурации. Например, вам, вероятно, следует разрешить '#' начинать комментарий до конца строки и игнорировать пустые строки. Вы также должны решить, что вы будете делать, если файл конфигурации содержит строку, которую вы не понимаете - игнорировать и продолжать молча или пожаловаться. (Я предпочитаю жалобы; тогда я знаю, почему то, что я только что добавил, не имеет никакого эффекта - тогда как тихое игнорирование означает, что я не знаю, имеет ли только что добавленная запись какой-либо эффект.)

Другая проблема - найти файл конфигурации. Делаете ли вы это путем компилирования, путем установки по умолчанию с переменной окружения, которую нужно переопределить, или с помощью какой-либо другой магии? Убедитесь, что есть опция командной строки, позволяющая указать файл конфигурации абсолютно - даже подумайте о том, чтобы сделать это единственным способом сделать это.

В противном случае, в широких пределах, оставайтесь простыми, и все будут счастливы.

person Jonathan Leffler    schedule 05.01.2009

Хорошо, вот реальный пример кода C:

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

Я запускаю его с этим входным файлом:

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.
person Charlie Martin    schedule 05.01.2009
comment
Выглядит очень хорошо. Как вы посоветуете обрабатывать каждую строку? Представьте, что первая строка может быть PORT = 1000, не могли бы вы как-нибудь создать функцию, которая возвращала бы эту строку и значение в виде структуры? Это может быть больше об архитектуре, чем просто о том, как это сделать. - person Filip Ekberg; 05.01.2009
comment
Почему bufr не является локальной переменной? Это вполне могло быть - и поэтому (в моей книге) должно быть. Я бы также использовал sizeof (bufr) в вызове fgets (). Я бы также проверил ошибку и завершил работу, чтобы избежать предложения distant else. Я бы также закрыл файл на случай, если код вырвется из функции. И Т. Д. - person Jonathan Leffler; 05.01.2009
comment
Хорошо, Филип, я думал об этом по крайней мере пять минут, и я совершенно не понимаю, почему вы предпочитаете иметь фиксированный буфер, выделенный в стеке, а не в пространстве данных, где не будет переполнения. пописать в коды возврата. Что касается других моментов, мех. Что бы ни. - person Charlie Martin; 05.01.2009
comment
@Jonathan, наличие буфера в качестве локальной переменной является аргументным. Скорее всего, он использовал это для упрощения примера. - person Filip Ekberg; 05.01.2009
comment
Ой. извини, Филип, это был спор с Джонатаном. Приносим извинения. - person Charlie Martin; 06.01.2009

Есть несколько способов. Вам не нужно использовать fgetc. Вам, вероятно, следует прочитать справочную страницу stdio, но каноническим решением было бы открыть файл с помощью fopen (3), а затем прочитать с помощью fgets (3), чтобы читать строку за раз. Это выглядело бы примерно так:

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

Вы также можете посмотреть libini на Sourceforge.

person Charlie Martin    schedule 05.01.2009
comment
Ах, понятно, это похоже на использование fgets в ASM (с которым я работал раньше). Было немного любопытно, можно ли использовать его с файловым потоком. Спасибо, очень признателен! - person Filip Ekberg; 05.01.2009
comment
Однако я хотел бы знать, как читать до следующей строки, нужно ли мне создавать это самому? Нет строки чтения, которая читается до \ n? или к любому другому токену - person Filip Ekberg; 05.01.2009
comment
Однако я не полностью удовлетворен ответом, так как он не дает мне понять, как я могу эффективно управлять другим :) - person Filip Ekberg; 05.01.2009
comment
fgets - это строка чтения. Когда вы выполняете fgets, указатель файла перемещается за следующую новую строку. Цикл, который я показываю выше, будет читать файл построчно. Секундочку, и я приведу реальный пример. - person Charlie Martin; 05.01.2009
comment
О, понятно, извините за то, что не понял этого! Очень хороший ответ! - person Filip Ekberg; 05.01.2009
comment
Вааай поздно, но в этом ответе есть огромный изъян. См. Почему «while (! Feof (file))» всегда неверно ? - person Andrew Henle; 14.01.2021

Зачем вам когда-либо писать этот код с нуля, это было сделано так много раз; просто найдите хорошую реализацию F / OSS и используйте ее.

Как вы читаете из текстового файла и эффективно обрабатываете каждую строку

Не беспокойтесь об эффективности, это не имеет значения для чтения файлов конфигурации. Вместо этого сделайте ставку на простоту и удобство обслуживания.

person Ana Betts    schedule 05.01.2009
comment
Если честно, у нас в университете есть задание на курс. где нам нужно создать приложение, но я не хочу сохранять конфигурацию в приложении, поэтому я хочу пойти еще дальше. Вот почему. И чтобы узнать, я хочу сделать это с нуля, сколько знаний я получу от использования готовых - person Filip Ekberg; 05.01.2009
comment
Да, и между прочим, Efficient - это своего рода абстрактное понятие, когда я использую эффективно, я имею в виду простоту + удобочитаемость и другие аспекты. Я также не хочу, чтобы это длилось вечно, конечно :) - person Filip Ekberg; 05.01.2009
comment
не могу даже представить, почему он был отвергнут, даже для домашнего задания это действенный и самый эффективный совет здесь ... - person Ilya; 05.01.2009
comment
В нем нет ничего, о чем я просил в своем вопросе, поэтому я не понимаю, почему за него когда-либо проголосуют. - person Filip Ekberg; 05.01.2009
comment
Ответ Пола Беттса - лучший совет: измените спецификации, потому что они неправильные. Часто это то, что мы должны говорить людям, даже если это их злит. - person bortzmeyer; 05.01.2009

Как бы я это сделал (псевдокод):

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

Обратите внимание, что часть кода (проверка пустых строк, комментариев и пробелов вокруг переменных) предназначена для обеспечения дополнительной гибкости при написании файла конфигурации. Например, вы не сломаете свою программу, случайно оставив лишнее место здесь и там.

Предполагается, что у вас есть файлы конфигурации, которые выглядят следующим образом:

# comment for option 1
option1 = value1

# comment for option 2
option2 = value2

...
person PolyThinker    schedule 05.01.2009

Еще одно решение - Pure-ftpd.

В отличие от многих демонов, Pure-FTPd не читает никаких конфигурационных файлов. Вместо этого он использует параметры командной строки. ... Добавление парсера для файлов конфигурации на сервере - плохая идея. Он все тормозит и даром не требует ресурсов.

А для опций есть getopt

person Malx    schedule 05.01.2009

Также есть функция getline () и друзья в GNU LibC

person dmityugov    schedule 05.01.2009

Некоторое время назад я написал чистый, независимый от зависимостей, управляемый событиями анализатор INI-файла на языке C. Я решил бросить его в общедоступный репозиторий Hg. Он лицензирован MIT, поэтому вы можете использовать его где угодно.

person Community    schedule 06.01.2009

C не такой простой, как Python, но мы все же хотим что-то вроде "импорта" ...

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

Вышеупомянутое основано на конфигурировании в x: y ...

ip:192.168.0.0.1
port:8888
person LinconFive    schedule 23.12.2019
comment
У этого ответа должно быть больше взлетов. Я тестировал его, и он более функциональный, учитывая обработку комментариев и LF. - person hypers; 19.04.2020
comment
Но вы, вероятно, захотите закрыть файл в конце. - person hypers; 20.04.2020

Вы думали о сохранении значений конфигурации как переменных среды? :) И файл конфигурации будет сценарием оболочки, который вы запускаете перед своей программой. (на самом деле сценарий оболочки выполнит его для сохранения переменных). Таким образом, вы используете оболочку в качестве парсера конфигурации :)

Используйте http://www.google.com/codesearch и прочтите конфигурацию

См. [Пример для httpd] [1]

[1]: https://web.archive.org/web/20120519035457/http://www.google.com:80/codesearch/p?hl=ru#W6G7kfsfmHo/httpd-2.2.4./server/config.c&q=read config httpd

person Malx    schedule 05.01.2009
comment
Это не рассматривается и кажется небезопасным, поскольку делает приложение менее портативным, верно? - person Filip Ekberg; 05.01.2009
comment
Почему нет? Вы можете использовать их в unix / win / mac. Просто переменная среды google mac или unix, победа. - person Malx; 05.01.2009
comment
Да, но вам придется установить все переменные envoirnment, что кажется сложным? Или это можно сделать, запрограммировав его .. - person Filip Ekberg; 05.01.2009
comment
Это может быть установлено с помощью сценария оболочки / летучей мыши или любой другой программы. Гугл: rc.conf - вот пример такого конфигурационного файла во FreeBSD. - person Malx; 05.01.2009
comment
Извините, но мне это не нравится. И вопрос был не совсем об альтернативных типах конфигурации - person Filip Ekberg; 05.01.2009