【C言語】glib2.0を使ってみた〜正規表現〜

# はじめに
C言語でアプリケーションを作成するとき、入出力の基本的な処理からコーディングするのは面倒ですし、バグの温床のなりえます。
良い解決策がないか調べたところ、glib2.0というライブラリが
* インストール・利用の容易さ
* 多機能
* ライセンス

という観点で、良さそうであると思いました。
一方、ネット上にあまり情報がなく、公式ドキュメントもわかりにくいということもあり、本記事にて基本的な使い方を紹介したいと思います。

今回は、**正規表現**について記載します。

# 動作環境
本記事は、下記の環境で動作を確認しています。
* Ubuntu18.04
* gcc

# インストール手順
下記コマンドでglib2.0一式をインストールすることができます。
Ubuntu18.04の場合、バージョン2.56.4が入るようです。
```bash
sudo apt install libglib2.0-dev
```

# 使用例

本記事では、glibを用いた正規表現の基本的な使い方として下記3つの使用例を示します。
なお、本記事では正規表現の説明は省略させていただきますので、他の詳細な解説記事を参考にしていただければと思います。

* 文字列抽出
* 文字列置換
* 名前つきグループによる文字列抽出

## 文字列抽出

まず基本的な文字列抽出の使用例を示します。
操作の流れとしては、

1. `g_regex_new`関数で正規表現のコンパイルパターンオブジェクトを作成
1. `g_regex_match`関数で入力文字列がコンパイルパターンにマッチするか判定
1. マッチしたら、`g_match_info_matches`関数と`g_match_info_next`関数でイテレーションしながら、マッチした文字列を取得する

という流れになります。

下記に示すのは、`Ichiro Suzuki, Satoshi Tanaka, Miwako Nakagawa, Keiko Suzuki\n`という文字列から、スズキが名字の氏名を抽出するサンプルソースです。

```c:main.c
#include <glib.h>

int main()
{
    gchar *str = "Ichiro Suzuki, Satoshi Tanaka, Miwako Nakagawa, Keiko Suzuki\n";
    //スズキが名字の名前パターンを作成
    gchar *pattern = "[A-z]+ Suzuki";

    GRegex *regex;
    GMatchInfo *match_info;
    GError *err = NULL;
 
    regex = g_regex_new(pattern, 0, 0, NULL);
    
    if (TRUE == g_regex_match(regex, str, 0, &match_info)) {
        g_print("str is matched.\n");

        while (g_match_info_matches(match_info)) {
            gchar *word = g_match_info_fetch(match_info, 0);
            g_print("Found: %s\n", word);
            g_free(word);
            g_match_info_next(match_info, &err);
        }
        if (NULL != err) {
            g_print("match error. msg=%s\n", err->message);
            g_error_free(err);
        }
    } else {
        g_print("str is not matched.\n");
    }

    //開放処理
    //g_regex_matchの結果がFALSEの場合も、match_infoはNULLではなくなるらしく、開放処理が必要
    g_match_info_free(match_info);
    g_regex_unref(regex);

    return 0;
}
```
下記のコマンドでコンパイルを行うことができます。
```bash
gcc main.c -o main `pkg-config --cflags --libs glib-2.0`
```

下記のコマンドで実行します。入力文字列の中から、スズキが名字の氏名を抽出することができました。
```bash
./main
str is matched.
Found: Ichiro Suzuki
Found: Keiko Suzuki
```

## 文字列置換

次に文字列置換の使用例を示します。
文字列置換には`g_regex_replace`関数を使います。
下記に示すのは、`mac1=00:11:22:aa:bb:cc, mac2=55:44:33:22:11:00,`という文字列に対し、MACアドレスの部分を`xxx`に置換するサンプルソースです。

```c:main.c
#include <glib.h>

int main()
{
    gchar *str = "mac1=00:11:22:aa:bb:cc, mac2=55:44:33:22:11:00,";
    //MACアドレスのパターンを作成
    gchar *pattern = "([0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5})";

    GRegex *regex;
    GError *err = NULL;
    gchar *replaced_str;
 
    regex = g_regex_new(pattern, 0, 0, NULL);
    
    //MACアドレス形式の文字列を見つけ、"xxx"に置換した結果を返す
    //第三引数は第二引数の文字列の長さ(NULL終端されていれば-1でよい)
    replaced_str = g_regex_replace(regex, str, -1, 0, "xxx", 0, &err);
    g_print("Replaced: %s\n", replaced_str);

    //開放処理
    if (NULL != err) {
        g_print("match error. msg=%s\n", err->message);
        g_error_free(err);
    }
    g_free(replaced_str);
    g_regex_unref(regex);

    return 0;
}
```
下記のコマンドでコンパイルを行うことができます。
```bash
gcc main.c -o main `pkg-config --cflags --libs glib-2.0`
```

下記のコマンドで実行します。入力文字列の中から、MACアドレスの文字列がxxxに置換されました。
```bash
./main
Replaced: mac1=xxx, mac2=xxx,
```

## 名前つきグループによる文字列抽出

次に名前つきグループによる文字列抽出の使用例を示します。
文字列の中で抽出したい文字列の個数と位置が明確に決まっている場合、`?P<名前>`でグループに名前をつけることができ、`g_match_info_fetch_named`でグループ名を指定することで、抽出した文字列を取得することができます。

下記に示すのは、`X-20.5_Y2.134.log`というファイル名からXとYに続く数値のみを抽出するサンプルソースです。

```c:main.c
#include <glib.h>

int main()
{
    gchar *str = "X-20.5_Y2.134.log";
    //ログファイルからXの後ろに続く数値とYの後ろに続く数値を
    //サブセットとして取り出すパターンを作成
    gchar *pattern = "X(?P<x>[+|-]?\\d+\\.?\\d*)_Y(?P<y>[+|-]?\\d+\\.?\\d*).log";

    GRegex *regex;
    GMatchInfo *match_info;
    gchar *x_str = NULL;
    gchar *y_str = NULL;
 
    regex = g_regex_new(pattern, 0, 0, NULL);
    
    if (TRUE == g_regex_match(regex, str, 0, &match_info)) {
        g_print("str is matched.\n");
        x_str = g_match_info_fetch_named(match_info, "x");
        y_str = g_match_info_fetch_named(match_info, "y");

        g_print("x=%s, y=%s\n", (NULL != x_str) ? x_str : "Not Found."
                              , (NULL != y_str) ? y_str : "Not Found.");

        g_free(x_str);
        g_free(y_str);
    } else {
        g_print("str is not matched.\n");
    }

    //開放処理
    //g_regex_matchの結果がFALSEの場合も、match_infoはNULLではなくなるらしく、開放処理が必要
    g_match_info_free(match_info);
    g_regex_unref(regex);

    return 0;
}
```

下記のコマンドでコンパイルを行うことができます。

```bash
gcc main.c -o main `pkg-config --cflags --libs glib-2.0`
```

下記のコマンドで実行します。入力文字列の中から、狙った数値のみを抽出することができました。

```bash
./main
str is matched.
x=-20.5, y=2.134
```

# あとがき
C言語は文字列処理のコーディングに向いておらず、入出力の基本的な処理を作るのは骨が折れます。glib2.0を活用することで、本質ではない部分に割く時間を削減し、本当に重要なことに注力できると思います。

# 参考文献
* [glib2.56manpage](https://www.manpagez.com/html/glib/glib-2.56.0/glib-Perl-compatible-regular-expressions.php)