【问题标题】:Creating a simple configuration file and parser in C++在 C++ 中创建一个简单的配置文件和解析器
【发布时间】:2011-10-17 02:10:53
【问题描述】:

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

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

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

string url, file;
bool true_false;

我已经做了一些研究,this 链接似乎有所帮助(nucleon 的帖子),但我似乎无法让它工作,而且它太复杂,我无法理解。有没有一种简单的方法可以做到这一点?我可以使用ifstream 加载文件,但这是我自己能做到的。谢谢!

【问题讨论】:

  • Boost.program_options 浮现在脑海中,它支持从命令行参数到配置文件的无缝转换。
  • 我听说过很多关于 boost 库的事情。我可能会尝试一下,但我希望使用字符串操作来做一些简单的事情。我不打算做任何繁重的配置文件。
  • 您是否考虑过将您的配置文件设为 XML,这样您就不必手动编写字符串解析器了?然后,您可以使用无数的 XML 库之一。
  • 现在是查找 boost 库的时候了 - boost.program_options 完全符合您的要求,而且非常简单。
  • 你见过this吗?我觉得很有趣!

标签: c++ file parsing configuration settings


【解决方案1】:

一般来说,解析这样典型的配置文件最容易分两个阶段:首先读取行,然后逐行解析。
在 C++ 中,可以使用 std::getline() 从流中读取行。虽然默认情况下它会读取到下一个'\n'(它将消耗,但不会返回),你也可以传递一些其他的分隔符,这使它成为读取最多一些字符的好候选,就像你的例子中的=

为简单起见,以下假设= 没有被空格包围。如果您想在这些位置允许空格,则必须在读取值之前策略性地放置 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);
  }
}

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

【讨论】:

    【解决方案2】:

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

    例如,如果您决定使用 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 章就足以满足您的需求。

    【讨论】:

    • 当然希望你的 config4star 有一个公共的 git repo,这样我就可以在另一个项目中使用它,而不是包含实际代码......
    • "据我所知,Config4* 是迄今为止世界上最好的配置文件解析器。竞争技术(如 XML、JSON、Java 属性、Windows 注册表等)是相比之下,平庸而简单。” - 由于您网站上的这种自命不凡的声明,没有下载。
    • @Zimano:“Config4* 入门指南”的第 4 章提供了充分而明确的证据来支持我的主张。此外,《Config4* 实际使用指南》第 8 章提供了相关案例研究。
    【解决方案3】:

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

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

    和链接:-lconfig++

    【讨论】:

    【解决方案4】:

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

    #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 个字怎么办?它会起作用吗?如果id == '#',它是跳过行,还是只是接下来的2个字符串?
    • @sop:是的,那是非常糟糕的代码。我做了一些改进。 Demo
    • @sop:(那时我不懂C++...)
    【解决方案5】:

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

    例如

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

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

    【讨论】:

      【解决方案6】:

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

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

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

      【讨论】:

      • JSON 或 XML 是机器可读的,但不是真正的人类可读的。
      • 如果格式正确,JSON 非常易于阅读(请参阅:docs.npmjs.com/files/package.json)。 XML 的可读性较差,但两者都被设计为人类可读/可编辑的。
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        这是一个只有头文件的 C++ 版本的 inih。

        inih (INI Not Invented Here) 是一个简单的 .INI 文件解析器 C. 它只有几页代码,它被设计成 小而简单,所以它适用于嵌入式系统。它也更多或 与 Python 的 .INI 文件的 ConfigParser 样式不太兼容, 包括 RFC 822 样式的多行语法和名称:值条目。

        【讨论】:

          【解决方案9】:

          所以我将上面的一些解决方案合并到我自己的解决方案中,这对我来说更有意义,变得更直观,更不容易出错。我使用公共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】:

            我正在寻找一个类似的简单 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】:

              以下是配置文件中“=”符号和数据之间的空白的简单解决方法。从 '=' 符号之后的位置分配给 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();
              }
              

              【讨论】:

              • 为什么我在使用你的方法时会出现分段错误(核心转储)?
              • 我的代码 sn-p 顶部的 cmets 中显示的表单中的输入吗?
              【解决方案12】:

              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 使用可变参数模板。这样你就可以传递你要读取的变量,并用相应的类型以适当的方式读取数据。

              【讨论】:

                【解决方案13】:

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

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

                文件.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
                

                【讨论】:

                  【解决方案14】:

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

                  【讨论】:

                    猜你喜欢
                    • 2017-05-10
                    • 2014-09-15
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-03-31
                    • 1970-01-01
                    相关资源
                    最近更新 更多