C プログラミングでの 設定ファイル読み込み

Windows だと iniファイルを GetPrivateProfileString などで読めるが、 C 単体だとそんなのない。 C で ini ファイルのパーサを作るか、何かライブラリを入れるしかない。そこで、 ini ファイルの変わりにシェルスクリプト環境変数を使うことを考える。

設定ファイル setting.sh

#!/bin/sh

# comment1
export APL_STR_VAR='abc #' # comment2
export APL_LONG_VAR=123    # comment3
export APL_DOUBLE_VAR=123.45
export APL_IP_ADDR_VAR=127.0.0.1
export APL_MAC_ADDR_VAR=01:02:03:04:05:06

起動スクリプト start_apl.sh

#!/bin/sh

source setting.sh
./a.out

設定ファイル setting.sh を起動スクリプト tart_apl.sh で読み取ってから、C プログラムを起動する。C プログラムでは、getenvで文字列を取得する。一旦起動スクリプトをはさんでいるのは、シェルプロンプトの環境変数を汚したくないから。設定値が少量であれば、シェルプロンプトで環境変数を定義してもよい。

設定ファイルをシェルスクリプトにすることによって、

  • シェル展開(`cmd`)、コメントアウトやシングル・ダブルクォーテーションが使える
  • 自分でパーサを書く必要がない
  • 設定値を簡単にファイル分割できる

この方法の問題点は、

  • 設定項目が大量にある場合、環境変数も大量になるので問題になるかも(メモリを食う)
  • 設定値が非常に長い場合も同様に問題になるかも
  • ini ファイルにおけるセクションの概念がない。ファイル分割しても、環境変数名で区別する必要がある。
#if 0
gcc -g $0 -o a.out && ./a.out test.ini; exit;
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

#define TRUE         1
#define FALSE        0
#define MAC_ADDR_LEN 6

#define NELEMS(array) (sizeof(array) / sizeof(array[0]))

typedef enum EnvType {
    STRING,
    LONG,
    DOUBLE,
    IP_ADDR,
    MAC_ADDR
} EnvType;

typedef struct Env {
    EnvType  type;
    char    *name;
    void    *data;
    int      len;
} Env;

char   g_string[256];
long   g_long;
double g_double;
u_int  g_ip_addr;
u_char g_mac_addr[MAC_ADDR_LEN];

Env g_env_table[] = {
    {STRING,   "APL_STR_VAR",      (void *)g_string,   sizeof(g_string)},
    {LONG,     "APL_LONG_VAR",     (void *)&g_long,    sizeof(long)},
    {DOUBLE,   "APL_DOUBLE_VAR",   (void *)&g_double,    sizeof(double)},
    {IP_ADDR,  "APL_IP_ADDR_VAR",  (void *)&g_ip_addr, sizeof(u_int)},
    {MAC_ADDR, "APL_MAC_ADDR_VAR", (void *)g_mac_addr, MAC_ADDR_LEN},
};

int   convert_mac_addr(char *data, u_char *mac_addr);
char *print_mac_addr(u_char *mac_addr, char *buf);
int   convert_ip_addr(char *data, u_int *ip_addr);
char *print_ip_addr(u_int ip_addr, char *buf);
int   convert_long(char *data, long *num);
int   convert_double(char *data, double *num);

int main(int argc, char *argv[])
{
    int   i;
    char *p;
    char  buf[256];

    for (i = 0; i < NELEMS(g_env_table); i++) {
        p = getenv(g_env_table[i].name);
        if (NULL == p) {
            fprintf(stderr, "Not found %s\n", g_env_table[i].name);
        }
        else {
            switch (g_env_table[i].type) {
            case STRING:
                strncpy((char *)g_env_table[i].data, p, g_env_table[i].len);
                printf("%s=%s\n",
                       g_env_table[i].name, (char *)g_env_table[i].data);
                break;
            case LONG:
                convert_long(p, (long *)g_env_table[i].data);
                printf("%s=%ld\n",
                       g_env_table[i].name, *(long *)g_env_table[i].data);
                break;
            case DOUBLE:
                convert_double(p, (double *)g_env_table[i].data);
                printf("%s=%lf\n",
                       g_env_table[i].name, *(double *)g_env_table[i].data);
                break;
            case IP_ADDR:
                convert_ip_addr(p, (u_int *)g_env_table[i].data);
                printf("%s=%s\n",
                       g_env_table[i].name,
                       print_ip_addr(*(u_int *)g_env_table[i].data, buf));
                break;
            case MAC_ADDR:
                convert_mac_addr(p, (u_char *)g_env_table[i].data);
                printf("%s=%s\n",
                       g_env_table[i].name,
                       print_mac_addr((u_char *)g_env_table[i].data, buf));
                break;
            default:
                fprintf(stderr,
                        "Unknown parameter type : %d\n",
                        g_env_table[i].type);
                break;
            }
        }
    }

    return 0;
}

int convert_long(char *data, long *num)
{
    long ret;
    char *e;

    ret = strtol(data, &e, 10);
    if(ERANGE != errno) {
        *num = ret;
        return TRUE;
    }
    else {
        return FALSE;
    }
}

int convert_double(char *data, double *num)
{
    double  ret;
    char   *e;

    ret = strtod(data, &e);
    if(ERANGE != errno) {
        *num = ret;
        return TRUE;
    }
    else {
        return FALSE;
    }
}

int convert_mac_addr(char *data, u_char *mac_addr)
{
    int   ret;
    int   i;
    u_int check_mac_addr[MAC_ADDR_LEN];

    ret = sscanf(data, "%02X:%02X:%02X:%02X:%02X:%02X",
                 &(check_mac_addr[0]),
                 &(check_mac_addr[1]),
                 &(check_mac_addr[2]),
                 &(check_mac_addr[3]),
                 &(check_mac_addr[4]),
                 &(check_mac_addr[5]));
    if(MAC_ADDR_LEN == ret) {
        for (i = 0; i < MAC_ADDR_LEN; i++) {
            mac_addr[i] = check_mac_addr[i];
        }
        return TRUE;
    }
    return FALSE;
}

char *print_mac_addr(u_char *mac_addr, char *buf)
{
    sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X",
           mac_addr[0],
           mac_addr[1],
           mac_addr[2],
           mac_addr[3],
           mac_addr[4],
           mac_addr[5]);
    return buf;
}

int convert_ip_addr(char *data, u_int *ip_addr)
{
    int ret;
    struct in_addr in_addr;

    ret = inet_pton(AF_INET, data, &in_addr);
    if (0 != ret) {
        *ip_addr = in_addr.s_addr;
        return TRUE;
    }
    return FALSE;
}

char *print_ip_addr(u_int ip_addr, char *buf)
{
    struct in_addr in_addr;
    char dest[INET_ADDRSTRLEN];

    memset(&in_addr, 0x00, sizeof(in_addr));
    in_addr.s_addr = ip_addr;
    if (NULL != inet_ntop(AF_INET, (void *)&in_addr, dest, INET_ADDRSTRLEN)) {
        sprintf(buf, "%s", dest);
    }
    else {
        buf = NULL;
    }

    return buf;
}

結果

$ sh start_apl.sh
APL_STR_VAR=abc #
APL_LONG_VAR=123
APL_DOUBLE_VAR=123.450000
APL_IP_ADDR_VAR=127.0.0.1
APL_MAC_ADDR_VAR=01:02:03:04:05:06