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

81

我正在尝试创建一个简单的配置文件,它的样子是这样的:

url = http://mysite.com
file = main.exe
true = 0
当程序运行时,我想要它将配置设置加载到下面列出的程序变量中。
string url, file;
bool true_false;
我已经做了一些研究,这个链接似乎有所帮助(核子的帖子),但我似乎无法使其正常工作,并且在我的部分上它过于复杂。是否有简单的方法?我可以使用ifstream来加载文件,但那就是我自己能达到的程度了。谢谢!

4
Boost.program_options让人想起,它支持从命令行参数无缝过渡到配置文件。 - Kerrek SB
我听说过许多关于boost库的事情。我可能会尝试使用它们,但我希望有些简单的字符串操作。我不打算处理任何重型配置文件。 - llk
1
你有没有考虑将配置文件制作成XML格式,这样就不必手动编写字符串解析器了?然后你可以使用众多现有的XML库之一。 - selbie
1
现在是查找boost库的时候了 - boost.program_options正好可以满足你的需求,并且非常简单易用。 - Tom
1
你看过这个吗?我觉得很有趣! - sop
15个回答

77

通常,解析这种典型的配置文件最容易分为两个阶段:首先读取行,然后逐行解析。


在C++中,可以使用std::getline()从流中读取行。默认情况下,它将读取到下一个'\n'(它会消耗掉但不返回),但是您也可以传递其他定界符,从而使它成为读取到某个字符的良好选择,例如您的示例中的=

为简单起见,以下假设=周围没有空格。如果要在这些位置允许空格,则必须在读取值之前策略性地放置is >> std::ws,并从键中去除尾随空格。然而,在我看来,对于配置文件阅读器而言,这种语法的稍微增加的灵活性并不值得麻烦。

#include <sstream>
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);
  }
}

(将错误处理添加作为读者的练习留下。)


41

正如其他人指出的那样,使用现有的配置文件解析库可能比重新发明轮子更省事。

例如,如果你决定使用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 网站 提供全面的文档,但仅阅读“入门指南”的第2章和第3章应该已经足够满足您的需求。


15
真希望你的 config4star 有一个公开的 Git 存储库,这样我就可以使用链接在另一个项目中使用它,而不是包含实际代码... - taxilian
40
据我所知,Config4*是目前世界上最好的配置文件解析器。与之竞争的技术(如XML、JSON、Java属性、Windows注册表等)相比较而言,它们都显得平庸和简单。由于你们网站上这种自命不凡的陈述,我并没有下载。 - Zimano
1
@Zimano:《Config4* 入门指南》第四章提供了充足且清晰的证据来支持我的说法。此外,《Config4* 实用使用指南》第八章还提供了一个相关的案例研究。 - Ciaran McHale

17

libconfig非常容易使用,更好的是,它使用伪JSON符号以提高可读性。

在Ubuntu上安装非常简单:sudo apt-get install libconfig++8-dev

并且链接:-lconfig++


3
那个链接是一个旧版本的分支。libconfig的官方网站在这里:https://hyperrealm.github.io/libconfig/,代码仓库在这里:https://github.com/hyperrealm/libconfig。 - ste

14

一个天真的方法可能是这样的:

#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


如果一个注释有超过2个单词怎么办?它会工作吗?它是跳过这一行,还是只跳过下面的2个字符串,如果id == '#' - sop
3
@sop:是的,那段代码相当糟糕。我进行了一些改进。Demo - Kerrek SB
@sop:(当时我不懂任何C++...) - Kerrek SB

6
将您的配置格式化为JSON,然后使用类似jsoncpp的库如何?例如:
{"url": "http://mysite dot com",
"file": "main.exe",
"true": 0}

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

3

3

我在寻找类似于Python模块ConfigParser的东西,发现了这个:https://github.com/jtilly/inih

这是一个仅包含头文件的C++版本的inih。

inih(INI非此地发明)是一个简单的.INI文件解析器,用C语言编写。它只有几页代码,并且被设计成小而简单,因此非常适合嵌入式系统。它也基本兼容Python的ConfigParser风格的.INI文件,包括RFC 822风格的多行语法和name:value条目。


2
为什么不尝试一些简单易读的格式,比如JSON(或XML)?与C ++相关的许多预制的开源实现可以使用,我会使用其中之一。如果你需要一些更“二进制”的东西,可以尝试使用BJSON或BSON :)

11
JSON 或 XML 可以被机器读取,但并不十分易于人类阅读。 - LtWorf
4
如果格式正确,JSON 相当易读(参见:https://docs.npmjs.com/files/package.json)。XML较不易读,但两者都被设计为易于人类阅读/编辑。 - Lèse majesté

2

所以我将上面的一些解决方案合并成了自己的,这对我来说更有意义,更直观,而且出错的可能性更小。我使用一个公共的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;            
    }
}

}


2

我正在寻找一个类似的简单C++配置文件解析器,这个教程网站为我提供了一个基本但可行的解决方案。它是一个快速而不太规范的解决方案,可以完成工作。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接