趣味的なIT・ネットの話題

Raspberry PiでIoTなシステム開発:libconfigを使ってみる

Cから作ったバイナリの挙動をコンパイルせずに変えたいので、コンフィギュレーションファイルを読み込む仕様にしようと思いたちましが。が、自分でパーサーを書くのは面倒くさい。そこで調べてみたのですが、この類のパーサーは山ほど有るんですね。とりあえず頻出になっているlibconfigを試用してみました。

インストール

マニュアルに何故か普通のインストール手順が書かれていません。半分推測でインストールしてみました。apt-getには無かったです。

pi@raspberrypi:~ $ wget http://www.hyperrealm.com/libconfig/libconfig-1.5.tar.gz
pi@raspberrypi:~ $ tar zxvf libconfig-1.5.tar.gz 
pi@raspberrypi:~ $ cd libconfig-1.5/
pi@raspberrypi:~/libconfig-1.5 $ ./configure
pi@raspberrypi:~/libconfig-1.5 $ make
pi@raspberrypi:~/libconfig-1.5 $ sudo make install

サンプルファイルの実行

最初のmakeで既にビルドが終わっていました。

pi@raspberrypi:~ $ cd libconfig-1.5/examples/c/
pi@raspberrypi:~/libconfig-1.5/examples/c $ ./example1
Store name: Books, Movies & More

TITLE                           AUTHOR                           PRICE   QTY
Treasure Island                 Robert Louis Stevenson          $ 29.99    5
Snow Crash                      Neal Stephenson                 $  9.99    8

TITLE                           MEDIA        PRICE   QTY
Brazil                          DVD         $ 19.99   11
The City of Lost Children       DVD         $ 18.99    5
Memento                         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 $ ls
example1    example1.o       example2    example2.o       example3    example3.o       example.cfg  Makefile.am
example1.c  example1.vcproj  example2.c  example2.vcproj  example3.c  example3.vcproj  Makefile     Makefile.in
pi@raspberrypi:~/libconfig-1.5/examples/c $ mv example.cfg example.ggg
pi@raspberrypi:~/libconfig-1.5/examples/c $ ls
example1    example1.o       example2    example2.o       example3    example3.o       example.ggg  Makefile.am
example1.c  example1.vcproj  example2.c  example2.vcproj  example3.c  example3.vcproj  Makefile     Makefile.in
pi@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 $ ./example1
No '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 $ ./example1
Store name: Books, Movies & More

TITLE                           AUTHOR                           PRICE   QTY
Treasure Island                 Robert Louis Stevenson          $ 29.99    5
Snow Crash                      Neal Stephenson                 $  9.99    8

TITLE                           MEDIA        PRICE   QTY
Brazil                          DVD         $ 19.99   11
The City of Lost Children       DVD         $ 18.99    5
Memento                         Blu-Ray     $ 24.99   20
Buckaroo 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倍ぐらい楽なので使ってみることにします。


Facebooktwitterpinterestlinkedinmail
納得したらすぐにシェア!