実用的なコマンドを作ってみよう

これまで数回に渡り、Linuxカーネルに特有の機能「inotify API」の具体的な使い方を紹介してきた。C言語がわからないと理解しづらい内容だったかもしれないが、カーネルに特有の機能を使うとこういったこともできる、ということは何となく感じてもらえたのではないかと思う。

ここまでだとただの”お勉強”になってしまうのだが、本連載の目的は建設的な努力によって将来発生する仕事を自動的にさばけるようにすることであり、「具体的にこういったことができる」という体験をする必要がある。というわけで今回は、inotify APIを使った具体的なコマンドを紹介しよう。

wait_untilfilechanges.c

例えば、これまで作成してきたC言語のソースコードをコンパイルする方法を考えてみよう。今までの説明であれば、ソースコードを編集したら、その都度ほかのターミナルに移動して、もしくはいったんエディタを終了して、ソースコードをビルド(make)していたと思う。しかし、この処理は決まりきったことであって、できることなら、ソースコードが保存された段階で自動的にビルドしてくれたほうが楽だ。

つまり、次のような繰り返し処理をサクッと書けると、作業がとても簡単になる。

while :
do
    ファイルAが変更されるまで待つ
    ファイルAをコンパイル
done

では、「ファイルAが変更されるまで待つ」を「wait_untilfilechanges」というコマンドにするとしよう。これを「wait_untilfilechanges target_file」のように実行して、「target_file」に書き込みが行われるまで待機するといった処理を行わせたい。つまり、次のような処理が書けるとよいということになる。

while :
do
    wait_untilfilechanges *.c
    make clean
    make
done

上記のように書いておくと、例えばviでソースコードを編集している間に「:w」で書き込みを実施すると、自動的にビルドが実施されたバイナリファイルが更新される、といった動作になる。作業が1つ楽になるわけだ。

この連載はプログラミングを説明するものではないので、ソースコードの書き方は省いて結果を掲載しておく。まず、今回のソースコード用に次のようなMakefileを用意する。

SRCS=   wait_untilfilechanges.c
CMD=    wait_untilfilechanges

OBJS=   ${SRCS:.c=.o}

all: ${OBJS}
    cc -o ${CMD} ${OBJS}

.SUFFIXES: .c .c

.c.o:
    cc -c $<

clean:
    rm -f ${OBJS} ${CMD}

「wait_untilfilechanges.c」の中身は次のようになる。

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>

int
main(int argc, char* argv[])
{
    int fd;
    int wd[1024];
    char buf[4096], *ptr;
    const struct inotify_event *event;
    ssize_t len;

    /* inotify API にアクセスするためのファイルディスクリプタを生成 */
    fd = inotify_init1(IN_NONBLOCK);

    /* モニタリング対象を追加 */
    for (int i = 1; i < argc; i++)
        /* イベントの種類はIN_MODIFY */
        wd[i] = inotify_add_watch(fd, argv[i], IN_ALL_EVENTS );

    /* モニタリングを実施 */
    for (;;) {
        /*
         * 複数のイベントをバッファに読み込み
         * ここでイベントが発生するまで処理が待ち状態になる
         */
        len = read(fd, buf, sizeof buf);

        /*
         * モニタリング対象が変更されていたら処理を終了する
         */
        ptr = buf;
        while (ptr < buf + len) {
            /* バッファにおけるイベントにアクセス */
            event = (const struct inotify_event *) ptr;

            /* イベント種類を確認し該当イベントだったら終了 */
            if (event->mask & (IN_MODIFY | IN_CLOSE)) {
                printf("----\n");
                exit(EXIT_SUCCESS);
            }

            /* バッファにおける次のイベントを指し示す  */
            ptr += sizeof(struct inotify_event) + event->len;
        }
    }

    exit(EXIT_SUCCESS);
}

これまで取り上げてきたソースコードよりも、だいぶシンプルになったことがおわかりいただけるのではないだろうか。wait_untilfilechangesは、「監視対象のファイルやディレクトリが変更されるまで待つ」という処理をするだけのコマンドだ。しかし、これが結構役に立つ。

wait_untilfilechangesの実行サンプル

wait_untilfilechanges.cのコンパイルや実行の例を見てみよう。まず、次のようにコンパイルを実施する。

wait_untilfilechanges.cのコンパイル

ビルドしたコマンドを実行してみよう。wait_untilfilechanges.cファイルを監視して、変更されたらmakeが実行されるという処理を延々と繰り返すというものだ。

ファイルが変更されたらビルドを実施する

次のようにソースコードを変更しながら、適当なタイミングでちょいちょい保存を実行する。

ソースコードを編集しつつ、時々保存する

保存したタイミングで、次のように先ほどwait_untilfilechangesを実行したターミナルではmakeが実行されることを確認できる。

ソースコードが保存されると自動的にmakeが実行される

こんな感じで、inotify APIを使って作成した簡単なコマンドを使うと、日常の作業の自動化ができるわけだ。自分の欲しい機能をコマンドとして実行することで、処理の自動化を進められる。

ここ数回の解説は、C言語を知らないとわかりづらい話だったかもしれない。だが、説明してきたように、多少なりともC言語を使えるとLinuxカーネルの機能を利用できるようになる。身に付けておいて損はしないし、将来楽をするために有用なスキルなので、時間を確保してぜひとも、学習に取り組んでいただければと思う。