81

I am trying to create a simple configuration file that looks like this

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

when the program runs, I would like it to load the configuration settings into the programs variables listed below.

string url, file;
bool true_false;

I have done some research and this link seemed to help (nucleon's post) but I can't seem to get it to work and it is too complicated to understand on my part. Is there a simple way of doing this? I can load the file using ifstream but that is as far as I can get on my own. Thanks!

llk
  • 2,501
  • 7
  • 36
  • 43
  • 4
    Boost.program_options comes to mind, that supports seamless transition from command-line arguments to a configuration file. – Kerrek SB Jul 31 '11 at 22:32
  • I have heard a lot about the boost libraries. I might give them a try but I was hoping for something simple using string operations. I don't plan on doing any heavy-duty configuration files. – llk Jul 31 '11 at 22:33
  • 1
    Have you considered making your config file as XML, so you don't have to manually write a string parser? Then you can use one of the countless number of XML libraries that are out there. – selbie Jul 31 '11 at 22:37
  • 1
    Now is the time to look up the boost libraries - boost.program_options does exactly what you want, and does it very simply. – Tom Aug 01 '11 at 00:29
  • 1
    Have you seen [this](http://www.dreamincode.net/forums/topic/183191-create-a-simple-configuration-file-parser/)? I found it interesting! – sop Sep 14 '15 at 15:52
  • Possible duplicate of [What c lib to use when I need to parse a simple config file under linux?](https://stackoverflow.com/q/2250607/608639) – jww Oct 31 '19 at 16:05

15 Answers15

75

In general, it's easiest to parse such typical config files in two stages: first read the lines, and then parse those one by one.
In C++, lines can be read from a stream using std::getline(). While by default it will read up to the next '\n' (which it will consume, but not return), you can pass it some other delimiter, too, which makes it a good candidate for reading up-to-some-char, like = in your example.

For simplicity, the following presumes that the = are not surrounded by whitespace. If you want to allow whitespaces at these positions, you will have to strategically place is >> std::ws before reading the value and remove trailing whitespaces from the keys. However, IMO the little added flexibility in the syntax is not worth the hassle for a config file reader.

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

(Adding error handling is left as an exercise to the reader.)

ntg
  • 12,950
  • 7
  • 74
  • 95
sbi
  • 219,715
  • 46
  • 258
  • 445
41

As others have pointed out, it will probably be less work to make use of an existing configuration-file parser library rather than re-invent the wheel.

For example, if you decide to use the Config4Cpp library (which I maintain), then your configuration file syntax will be slightly different (put double quotes around values and terminate assignment statements with a semicolon) as shown in the example below:

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

The following program parses the above configuration file, copies the desired values into variables and prints them:

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

The Config4Cpp website provides comprehensive documentation, but reading just Chapters 2 and 3 of the "Getting Started Guide" should be more than sufficient for your needs.

Ciaran McHale
  • 2,126
  • 14
  • 21
  • 15
    Sure wish your config4star had a public git repo so I could use it in another project with a link instead of including the actual code... – taxilian Sep 29 '14 at 20:15
  • 40
    "As far as I know, Config4* is by far the best configuration-file parser in the world. Competing technologies (such as XML, JSON, Java properties, the Windows Registry and so on) are mediocre and simplistic in comparison." - Didn't download because of this pretentious statement on your website. – Zimano May 05 '17 at 07:44
  • 1
    @Zimano: Chapter 4 of the "Config4* Getting Started Guide" provides ample and clear evidence to back up my claim. In addition, Chapter 8 of the "Config4* Practical Usage Guide" provides a relevant case study. – Ciaran McHale Feb 21 '20 at 19:20
17

libconfig is very easy, and what's better, it uses a pseudo json notation for better readability.

Easy to install on Ubuntu: sudo apt-get install libconfig++8-dev

and link: -lconfig++

  • 3
    That link is to an old fork. The libconfig site is here: https://hyperrealm.github.io/libconfig/ and the repo is here: https://github.com/hyperrealm/libconfig – ste Apr 01 '20 at 10:36
14

A naive approach could look like this:

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

Now you can access each option value from the global options map anywhere in your program. If you want castability, you could make the mapped type a boost::variant.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
6

How about formatting your configuration as JSON, and using a library like jsoncpp?

e.g.

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

You can then read it into named variables, or even store it all in a std::map, etc. The latter means you can add options without having to change and recompile your configuration parser.

patmanpato
  • 823
  • 1
  • 11
  • 15
3

I've searched config parsing libraries for my project recently and found these libraries:

Ivan Samygin
  • 4,210
  • 1
  • 20
  • 33
3

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.

Aaron Swan
  • 1,206
  • 1
  • 13
  • 20
2

So I merged some of the above solutions into my own, which for me made more sense, became more intuitive and a bit less error prone. I use a public stp::map to keep track of the possible config ids, and a struct to keep track of the possible values. Her it goes:

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

}

tronic
  • 220
  • 1
  • 10
2

I was searching for a similar simple C++ config file parser and this tutorial website provided me with a basic yet working solution. Its quick and dirty soultion to get the job done.

myConfig.txt

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

The following program reads the previous configuration file:

#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();
}
Shan
  • 511
  • 5
  • 13
2

Why not trying something simple and human-readable, like JSON (or XML) ?

There are many pre-made open-source implementations of JSON (or XML) for C++ - I would use one of them.

And if you want something more "binary" - try BJSON or BSON :)

Roman Pietrzak
  • 635
  • 3
  • 15
  • 11
    JSON or XML are machine readable but not really human readable. – LtWorf Feb 22 '14 at 10:12
  • 4
    JSON is pretty human-readable if you format it properly (see: https://docs.npmjs.com/files/package.json). XML is less readable, but both are designed to be both human readable/editable. – Lèse majesté Oct 25 '16 at 18:44
1

Here is a simple work around for white space between the '=' sign and the data, in the config file. Assign to the istringstream from the location after the '=' sign and when reading from it, any leading white space is ignored.

Note: while using an istringstream in a loop, make sure you call clear() before assigning a new string to it.

//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();
}
Hari
  • 1,561
  • 4
  • 17
  • 26
1

I would like to recommend a single header C++ 11 YAML parser mini-yaml.

A quick-start example taken from the above repository.

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

Output

foo bar
hello world
123
1
integer: 123
boolean: true
Hanzhe Teng
  • 73
  • 1
  • 6
1

No one mentioned <regex>. I prefer them since they are really easy to read and less error prone. MWE:

#include <fstream>
#include <iostream>
#include <regex>
#include <string>

struct config_t
{
    // (define variables here)

    void read_from(const std::string& fname)
    {
        std::ifstream cfg_file(fname);
        if(!cfg_file.good())
            throw std::runtime_error("Cannot open file: " + fname);

        std::string line;
        while(std::getline(cfg_file, line))
        {
            std::regex re(R"XXX(^(\s*(\S+)\s*=\s*(\S+))?\s*(#.*)?$)XXX",
                std::regex::optimize);
            std::smatch match;
            if(std::regex_search(line, match, re))
            {
                if(match.length(2))
                {
                    std::string key = match.str(2),
                        value = match.str(3);
                    std::cout << "key-value-pair: " << key << " -> " << value << std::endl;
                    // (fill variables here)
                }
            }
            else
                throw std::runtime_error("Invalid line: " + line);
        }
    }
};

int main(int argc, char** argv)
{
    int rval = EXIT_SUCCESS;
    try
    {
        config_t cfg;
        if (argc != 2)
            throw std::runtime_error("Expecting exactly one argument");
        cfg.read_from(argv[1]);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        rval = EXIT_FAILURE;
    }
    return rval;
}
Johannes
  • 2,901
  • 5
  • 30
  • 50
0

SimpleConfigFile is a library that does exactly what you require and it is very simple to use.

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

The following program reads the previous configuration file:

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

The function CFG::ReadFile uses variadic templates. This way, you can pass the variables you want to read and the corresponding type is used for reading the data in the appropriate way.

fbarber
  • 1
  • 1
0

Using the above answer by Shan, I did some simple modification for easy data reading from files like- multiple inputs in a single line, option for comment with '#' after inputs.

The following is the input file config.txt

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

Here is the code,

#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;
}
subrata
  • 111
  • 3
  • 8