用C ++创建一个简单的配置文件和解析器

时间:2011-07-31 22:28:28

标签: c++ file parsing configuration settings

我正在尝试创建一个看起来像这样的简单配置文件

url = http://mysite.com
file = main.exe
true = 0

当程序运行时,我希望它将配置设置加载到下面列出的程序变量中。

string url, file;
bool true_false;

我做了一些研究,this链接似乎有帮助(核子的帖子),但我似乎无法让它工作,而且我太难理解了。有一个简单的方法吗?我可以使用ifstream加载文件,但这是我自己可以得到的。谢谢!

14 个答案:

答案 0 :(得分:47)

通常,最简单的方法是分两个阶段解析这些典型的配置文件:首先读取这些行,然后逐个解析它们。
在C ++中,可以使用std::getline()从流中读取行。虽然默认情况下它会读取到下一个'\n'(它将消耗,但不会返回),您也可以将其传递给其他分隔符,这使其成为阅读某些分隔符的良好候选者。 char,就像你的例子中的=一样。

为简单起见,以下假设= 被空格包围。如果要在这些位置允许空格,则必须在读取值之前策略性地放置is >> std::ws并从键中删除尾随空格。但是,IMO在语法上增加了一点灵活性并不值得为配置文件阅读器带来麻烦。

const char config[] = "url=http://example.com\n"
                      "file=main.exe\n"
                      "true=0";

std::istringstream is_file(config);

std::string line;
while( std::getline(is_file, line) )
{
  std::istringstream is_line(line);
  std::string key;
  if( std::getline(is_line, key, '=') )
  {
    std::string value;
    if( std::getline(is_line, value) ) 
      store_line(key, value);
  }
}

(添加错误处理留给读者练习。)

答案 1 :(得分:32)

正如其他人所指出的那样,使用现有的配置文件解析器库而不是重新发明轮子可能会少一些工作。

例如,如果您决定使用Config4Cpp库(我维护),那么您的配置文件语法会略有不同(在值周围加上双引号并以分号结束赋值语句),如图所示以下示例:

# File: someFile.cfg
url = "http://mysite.com";
file = "main.exe";
true_false = "true";

以下程序解析上述配置文件,将所需的值复制到变量中并打印出来:

#include <config4cpp/Configuration.h>
#include <iostream>
using namespace config4cpp;
using namespace std;

int main(int argc, char ** argv)
{
    Configuration *  cfg = Configuration::create();
    const char *     scope = "";
    const char *     configFile = "someFile.cfg";
    const char *     url;
    const char *     file;
    bool             true_false;

    try {
        cfg->parse(configFile);
        url        = cfg->lookupString(scope, "url");
        file       = cfg->lookupString(scope, "file");
        true_false = cfg->lookupBoolean(scope, "true_false");
    } catch(const ConfigurationException & ex) {
        cerr << ex.c_str() << endl;
        cfg->destroy();
        return 1;
    }
    cout << "url=" << url << "; file=" << file
         << "; true_false=" << true_false
         << endl;
    cfg->destroy();
    return 0;
}

Config4Cpp website提供了全面的文档,但只阅读“入门指南”的第2章和第3章应该足以满足您的需求。

答案 2 :(得分:13)

天真的方法可能如下所示:

#include <map>
#include <sstream>
#include <stdexcept>
#include <string>

std::map<std::string, std::string> options; // global?

void parse(std::istream & cfgfile)
{
    for (std::string line; std::getline(cfgfile, line); )
    {
        std::istringstream iss(line);
        std::string id, eq, val;

        bool error = false;

        if (!(iss >> id))
        {
            error = true;
        }
        else if (id[0] == '#')
        {
            continue;
        }
        else if (!(iss >> eq >> val >> std::ws) || eq != "=" || iss.get() != EOF)
        {
            error = true;
        }

        if (error)
        {
            // do something appropriate: throw, skip, warn, etc.
        }
        else
        {
            options[id] = val;
        }
    }
}

现在,您可以从程序中的任意位置访问全局options地图中的每个选项值。如果您想要可投射性,可以将映射类型设为boost::variant

答案 3 :(得分:12)

libconfig非常简单,而且更好的是,它使用伪json表示法来提高可读性。

易于在Ubuntu上安装:sudo apt-get install libconfig++8-dev

并链接:-lconfig++

答案 4 :(得分:4)

为什么不尝试简单且易读的东西,比如JSON(或XML)?

C ++有许多预制的JSON(或XML)开源实现 - 我会使用其中一个。

如果你想要更多“二元”的东西 - 试试BJSON或BSON:)

答案 5 :(得分:3)

我最近搜索了我的项目的配置解析库,并找到了这些库:

答案 6 :(得分:3)

如何将配置格式化为JSON,并使用像jsoncpp这样的库?

e.g。

{"url": "http://mysite dot com",
"file": "main.exe",
"true": 0}

然后您可以将其读入命名变量,甚至将它们全部存储在std :: map等中。后者意味着您可以添加选项而无需更改和重新编译配置解析器。

答案 7 :(得分:2)

I was looking for something that worked like the python module ConfigParser and found this: https://github.com/jtilly/inih

This is a header only C++ version of inih.

inih (INI Not Invented Here) is a simple .INI file parser written in C. It's only a couple of pages of code, and it was designed to be small and simple, so it's good for embedded systems. It's also more or less compatible with Python's ConfigParser style of .INI files, including RFC 822-style multi-line syntax and name: value entries.

答案 8 :(得分:1)

这是对&#39; =&#39;之间的空白区域的简单解决方法。在配置文件中签名和数据。在&#39; =&#39;之后从位置分配给istringstream。标志,当从中读取时,任何前导空格都会被忽略。

注意:在循环中使用istringstream时,请确保在为其指定新字符串之前调用clear()。

//config.txt
//Input name = image1.png
//Num. of rows = 100
//Num. of cols = 150

std::string ipName;
int nR, nC;

std::ifstream fin("config.txt");
std::string line;
std::istringstream sin;

while (std::getline(fin, line)) {
 sin.str(line.substr(line.find("=")+1));
 if (line.find("Input name") != std::string::npos) {
  std::cout<<"Input name "<<sin.str()<<std::endl;
  sin >> ipName;
 }
 else if (line.find("Num. of rows") != std::string::npos) {
  sin >> nR;
 }
 else if (line.find("Num. of cols") != std::string::npos) {
  sin >> nC;
 }
 sin.clear();
}

答案 9 :(得分:1)

因此,我将上述一些解决方案合并到自己的解决方案中,这对我来说更有意义,变得更加直观,并且不易出错。我使用公共stp::map来跟踪可能的配置ID,并使用struct来跟踪可能的值。她去了:

struct{
    std::string PlaybackAssisted = "assisted";
    std::string Playback = "playback";
    std::string Recording = "record";
    std::string Normal = "normal";
} mode_def;

std::map<std::string, std::string> settings = {
    {"mode", mode_def.Normal},
    {"output_log_path", "/home/root/output_data.log"},
    {"input_log_path", "/home/root/input_data.log"},
};

void read_config(const std::string & settings_path){
std::ifstream settings_file(settings_path);
std::string line;

if (settings_file.fail()){
    LOG_WARN("Config file does not exist. Default options set to:");
    for (auto it = settings.begin(); it != settings.end(); it++){
        LOG_INFO("%s=%s", it->first.c_str(), it->second.c_str());
    }
}

while (std::getline(settings_file, line)){
    std::istringstream iss(line);
    std::string id, eq, val;

    if (std::getline(iss, id, '=')){
        if (std::getline(iss, val)){
            if (settings.find(id) != settings.end()){
                if (val.empty()){
                    LOG_INFO("Config \"%s\" is empty. Keeping default \"%s\"", id.c_str(), settings[id].c_str());
                }
                else{
                    settings[id] = val;
                    LOG_INFO("Config \"%s\" read as \"%s\"", id.c_str(), settings[id].c_str());
                }
            }
            else{ //Not present in map
                LOG_ERROR("Setting \"%s\" not defined, ignoring it", id.c_str());
                continue;
            }
        }
        else{
            // Comment line, skiping it
            continue;
        }
    }
    else{
        //Empty line, skipping it
        continue;            
    }
}

}

答案 10 :(得分:1)

我正在寻找类似的简单C ++配置文件解析器,this tutorial website为我提供了一个基本但有效的解决方案。快速而肮脏的灵魂去完成工作。

myConfig.txt

gamma=2.8
mode  =  1
path = D:\Photoshop\Projects\Workspace\Images\

以下程序读取先前的配置文件:

#include <iostream>
#include <fstream>
#include <algorithm>
#include <string>

int main()
{
    double gamma = 0;
    int mode = 0;
    std::string path;

    // std::ifstream is RAII, i.e. no need to call close
    std::ifstream cFile("myConfig.txt");
    if (cFile.is_open())
    {
        std::string line;
        while (getline(cFile, line)) 
        {
            line.erase(std::remove_if(line.begin(), line.end(), isspace),line.end());
            if (line[0] == '#' || line.empty()) continue;

            auto delimiterPos = line.find("=");
            auto name = line.substr(0, delimiterPos);
            auto value = line.substr(delimiterPos + 1);

            //Custom coding
            if (name == "gamma") gamma = std::stod(value);
            else if (name == "mode") mode = std::stoi(value);
            else if (name == "path") path = value;
        }
    }
    else 
    {
        std::cerr << "Couldn't open config file for reading.\n";
    }

    std::cout << "\nGamma=" << gamma;
    std::cout << "\nMode=" << mode;
    std::cout << "\nPath=" << path;
    std::getchar();
}

答案 11 :(得分:0)

SimpleConfigFile是一个完全满足您要求的库,使用非常简单。

# File file.cfg
url = http://example.com
file = main.exe
true = 0 

以下程序读取先前的配置文件:

#include<iostream>
#include<string>
#include<vector>
#include "config_file.h"

int main(void)
{
    // Variables that we want to read from the config file
    std::string url, file;
    bool true_false;

    // Names for the variables in the config file. They can be different from the actual variable names.
    std::vector<std::string> ln = {"url","file","true"};

    // Open the config file for reading
    std::ifstream f_in("file.cfg");

    CFG::ReadFile(f_in, ln, url, file, true_false);
    f_in.close();

    std::cout << "url: " << url << std::endl;
    std::cout << "file: " << file << std::endl;
    std::cout << "true: " << true_false << std::endl;

    return 0;
}

函数CFG::ReadFile使用可变参数模板。这样,您可以传递要读取的变量,并使用相应的类型以适当的方式读取数据。

答案 12 :(得分:0)

我想推荐一个单头 C++ 11 YAML 解析器mini-yaml

取自上述存储库的快速入门示例。

file.txt

key: foo bar
list:
  - hello world
  - integer: 123
    boolean: true

.cpp

Yaml::Node root;
Yaml::Parse(root, "file.txt");

// Print all scalars.
std::cout << root["key"].As<std::string>() << std::endl;
std::cout << root["list"][0].As<std::string>() << std::endl;
std::cout << root["list"][1]["integer"].As<int>() << std::endl;
std::cout << root["list"][1]["boolean"].As<bool>() << std::endl;

// Iterate second sequence item.
Node & item = root[1];
for(auto it = item.Begin(); it != item.End(); it++)
{
    std::cout << (*it).first << ": " << (*it).second.As<string>() << std::endl;
}

输出

foo bar
hello world
123
1
integer: 123
boolean: true

答案 13 :(得分:0)

使用 Shan 的上述答案,我做了一些简单的修改,以便从文件中轻松读取数据,例如单行中的多个输入,输入后带有“#”的注释选项。

以下是输入文件config.txt

# comments with #
# inputs can be separeted by comma
name=S.Das, age=28 #details
weight=65

这是代码,

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
using std::istringstream;

using std::string;

void readinput(std::unordered_map<string, string>& data) {
    // std::ifstream is RAII, i.e. no need to call close
    std::ifstream cFile("config.txt");
    if (cFile.is_open()) {
        std::string line;
        while (getline(cFile, line)) {
            line.erase(std::remove_if(line.begin(), line.end(), isspace), line.end());
            if (line[0] == '#' || line.empty()) {
                continue;
            } else if (line.find('#')) {
                line = line.substr(0, line.find('#'));
            }
            std::istringstream iss(line);
            string strr;
            while (getline(iss, strr, ',')) {
                auto delimiterPos = strr.find("=");
                auto name         = strr.substr(0, delimiterPos);
                string value      = strr.substr(delimiterPos + 1);
                // std::cout << name << " " << value << '\n';
                data[name] = value;
            }
        }
    } else {
        std::cerr << "Couldn't open config file for reading.\n";
    }
}

int main() {
    std::unordered_map<string, string> data;
    readinput(data);
    std::cout << data.at("age") << std::endl;
    return 0;
}