Cから作ったバイナリの挙動をコンパイルせずに変えたいので、コンフィギュレーションファイルを読み込む仕様にしようと思いたちましが。が、自分でパーサーを書くのは面倒くさい。そこで調べてみたのですが、この類のパーサーは山ほど有るんですね。とりあえず頻出になっているlibconfigを試用してみました。
インストール
マニュアルに何故か普通のインストール手順が書かれていません。半分推測でインストールしてみました。apt-getには無かったです。
pi@raspberrypi:~ $ wget http://www.hyperrealm.com/libconfig/libconfig-1.5.tar.gzpi@raspberrypi:~ $ tar zxvf libconfig-1.5.tar.gz pi@raspberrypi:~ $ cd libconfig-1.5/pi@raspberrypi:~/libconfig-1.5 $ ./configurepi@raspberrypi:~/libconfig-1.5 $ makepi@raspberrypi:~/libconfig-1.5 $ sudo make install
サンプルファイルの実行
最初のmakeで既にビルドが終わっていました。
pi@raspberrypi:~ $ cd libconfig-1.5/examples/c/pi@raspberrypi:~/libconfig-1.5/examples/c $ ./example1Store name: Books, Movies & MoreTITLE AUTHOR PRICE QTYTreasure Island Robert Louis Stevenson $ 29.99 5Snow Crash Neal Stephenson $ 9.99 8TITLE MEDIA PRICE QTYBrazil DVD $ 19.99 11The City of Lost Children DVD $ 18.99 5Memento Blu-Ray $ 24.99 20
サンプルのコンフィギュレーションファイルの中身がこちら。
// An example configuration file that stores information about a store.// Basic store information:name = ""Books, Movies & More"";// Store inventory:inventory ={ books = ( { title = ""Treasure Island""; author = ""Robert Louis Stevenson""; price = 29.99; qty = 5; }, { title = ""Snow Crash""; author = ""Neal Stephenson""; price = 9.99; qty = 8; } ); movies = ( { title = ""Brazil""; media = ""DVD""; price = 19.99; qty = 11; }, { title = ""The City of Lost Children""; media = ""DVD""; price = 18.99; qty = 5; }, { title = ""Memento""; media = ""Blu-Ray""; price = 24.99; qty = 20; }, { title = ""Howard the Duck""; } );};// Store hours:hours ={ mon = { open = 9; close = 18; }; tue = { open = 9; close = 18; }; wed = { open = 9; close = 18; }; thu = { open = 9; close = 18; }; fri = { open = 9; close = 20; }; sat = { open = 9; close = 20; }; sun = { open = 11; close = 16; };};
ファイルが存在しない場合のエラー処理
ファイルが無い場合のエラーに対応できます。
config_t cfg; config_setting_t *setting; const char *str; config_init(&cfg); /* Read the file. If there is an error, report it and exit. */ if(! config_read_file(&cfg, ""example.cfg"")) { fprintf(stderr, ""%s:%d - %s\n"", config_error_file(&cfg), config_error_line(&cfg), config_error_text(&cfg)); config_destroy(&cfg); return(EXIT_FAILURE); }
ファイル名を変えてみたらこんな感じに。
pi@raspberrypi:~/libconfig-1.5/examples/c $ lsexample1 example1.o example2 example2.o example3 example3.o example.cfg Makefile.amexample1.c example1.vcproj example2.c example2.vcproj example3.c example3.vcproj Makefile Makefile.inpi@raspberrypi:~/libconfig-1.5/examples/c $ mv example.cfg example.gggpi@raspberrypi:~/libconfig-1.5/examples/c $ lsexample1 example1.o example2 example2.o example3 example3.o example.ggg Makefile.amexample1.c example1.vcproj example2.c example2.vcproj example3.c example3.vcproj Makefile Makefile.inpi@raspberrypi:~/libconfig-1.5/examples/c $ ./example1(null):0 - file I/O error
単一キーを探して値を取得
シンプルな記法ですね。配列ではない要素を取得する場合はconfig_lookup_string()。
/* Get the store name. */ if(config_lookup_string(&cfg, ""name"", &str)) printf(""Store name: %s\n\n"", str); else fprintf(stderr, ""No 'name' setting in configuration file.\n"");
cfgの中のnameをnamuに変えてみると。
pi@raspberrypi:~/libconfig-1.5/examples/c $ ./example1No 'name' setting in configuration file.
連想配列である多次元配列
連想配列と通常の配列の多次元配列を取得。配列ではないデータはconfig_t型から直接参照していたけれども、配列は一次の名称で指定してconfig_setting_t型の変数をセットし、これをキーにしてデータを取得する。多次元配列なのでconfig_setting_t型の変数をまたセットして、中身にアクセスする。config_setting_length()で要素数が取得できるので繰り返し。配列中の行数で取得する場合はconfig_setting_get_elem()、要素名で取得する場合はconfig_setting_lookup_string()。
/* Output a list of all books in the inventory. */ setting = config_lookup(&cfg, ""inventory.books""); if(setting != NULL) { int count = config_setting_length(setting); int i; printf(""%-30s %-30s %-6s %s\n"", ""TITLE"", ""AUTHOR"", ""PRICE"", ""QTY""); for(i = 0; i < count; ++i) { config_setting_t *book = config_setting_get_elem(setting, i); /* Only output the record if all of the expected fields are present. */ const char *title, *author; double price; int qty; if(!(config_setting_lookup_string(book, ""title"", &title) && config_setting_lookup_string(book, ""author"", &author) && config_setting_lookup_float(book, ""price"", &price) && config_setting_lookup_int(book, ""qty"", &qty))) continue; printf(""%-30s %-30s $%6.2f %3d\n"", title, author, price, qty); } putchar('\n'); }
メモリ解放
config_init()で確保したメモリを、config_destroy()で解放。が、マニュアルには""deallocating all memory associated with the configuration, but does not attempt to deallocate theconfig_t structure itself.""と書かれていて解放しているのか解放していないのかよく分かりません。どういう意味だろう。
config_destroy(&cfg); return(EXIT_SUCCESS);
設定ファイルに加筆
example2.cが既存ファイルに加筆をするサンプル
/* Find the 'movies' setting. Add intermediate settings if they don't yet * exist. */ root = config_root_setting(&cfg); setting = config_setting_get_member(root, ""inventory""); if(!setting) setting = config_setting_add(root, ""inventory"", CONFIG_TYPE_GROUP); setting = config_setting_get_member(setting, ""movies""); if(!setting) setting = config_setting_add(setting, ""movies"", CONFIG_TYPE_LIST); /* Create the new movie entry. */ movie = config_setting_add(setting, NULL, CONFIG_TYPE_GROUP); setting = config_setting_add(movie, ""title"", CONFIG_TYPE_STRING); config_setting_set_string(setting, ""Buckaroo Banzai""); setting = config_setting_add(movie, ""media"", CONFIG_TYPE_STRING); config_setting_set_string(setting, ""DVD""); setting = config_setting_add(movie, ""price"", CONFIG_TYPE_FLOAT); config_setting_set_float(setting, 12.99); setting = config_setting_add(movie, ""qty"", CONFIG_TYPE_INT); config_setting_set_float(setting, 20);
実行するとupdated.cfgが生成される。さっきのexample1の対象のファイルを修正してからmake
/* Read the file. If there is an error, report it and exit. */ if(! config_read_file(&cfg, ""updated.cfg""))
確かに追加されている。
pi@raspberrypi:~/libconfig-1.5/examples/c $ ./example1Store name: Books, Movies & MoreTITLE AUTHOR PRICE QTYTreasure Island Robert Louis Stevenson $ 29.99 5Snow Crash Neal Stephenson $ 9.99 8TITLE MEDIA PRICE QTYBrazil DVD $ 19.99 11The City of Lost Children DVD $ 18.99 5Memento Blu-Ray $ 24.99 20Buckaroo Banzai DVD $ 12.99 0
設定ファイルの中身を見ても確かに変更されている。フォーマッティングが変更されてしまうのが難かな。
name = ""Books, Movies & More""inventory = { books = ( { title = ""Treasure Island"" author = ""Robert Louis Stevenson"" price = 29.99 qty = 5 }, { title = ""Snow Crash"" author = ""Neal Stephenson"" price = 9.99 qty = 8 } ) movies = ( { title = ""Brazil"" media = ""DVD"" price = 19.99 qty = 11 }, { title = ""The City of Lost Children"" media = ""DVD"" price = 18.99 qty = 5 }, { title = ""Memento"" media = ""Blu-Ray"" price = 24.99 qty = 20 }, { title = ""Howard the Duck"" }, { title = ""Buckaroo Banzai"" media = ""DVD"" price = 12.99 qty = 0 } )}hours = { mon = { open = 9 close = 18 } tue = { open = 9 close = 18 } wed = { open = 9 close = 18 } thu = { open = 9 close = 18 } fri = { open = 9 close = 20 } sat = { open = 9 close = 20 } sun = { open = 11 close = 16 }}
いずれにせよ自分でパーサーの仕様を考えて書くより200倍ぐらい楽なので使ってみることにします。"""