Plan 9とGo言語のブログ

主にPlan 9やGo言語の日々気づいたことを書きます。

Plan 9 CのARGBEGIN、ARGENDマクロ

Plan 9のCは独自のライブラリを持っていて、stdio.hのようなANSIライブラリもAPE(ANSI/POSIX Environment)として存在してはいるが、基本的にはPlan 9独自のライブラリを使う方が好まれる。ANSI Cとの違いは挙げればいくつもあるのでPlan 9 Cを知らない人には伝わりづらいけれども、Goの標準ライブラリにもPlan 9のCライブラリの面影が部分的に残っているので、Goの基礎的なパッケージ(bufio, utf8, strconv, stringsなど)に似ているといえば伝わるかもしれない。いくつか挙げると、Plan 9 Cではバッファリングを行うライブラリはlibbioにまとまっているが、libbio

#include <bio.h>

int Binit(Biobuf *bp, int fd, int mode);
int Brdline(Biobufhdr *bp, int delim);

は、ほとんどGoの

package bufio

func NewReader(rd io.Reader) *Reader
func (b *Reader) ReadBytes(delim byte) ([]byte, error)

と同じ使用感になっているし、

#include <libc.h>

int tokenize(char *str, char **args, int maxargs);

も、言語の違いこそあるものの、Goのstrings.Fieldsとだいたい同じように使える。また、UTF-8Plan 9から出てきたものなので当然といえば当然だけど、Plan 9 CはGoと同様に、Unicode文字を扱う場合はRune*1を使う。他にも探せば似たものはいくつもあるけれど、詳細には取り挙げない。

コマンドラインパーサ

Plan 9独自のCライブラリには、コマンドラインオプションを解析するARGBEGINマクロとARGENDマクロがある。これらはコマンドラインオプションを扱うときに使用するマクロで、典型的にはEARGFなどと組み合わせて以下のように使う。

#include <u.h>
#include <libc.h>

int debug;
char *file;

void
usage(void)
{
    fprint(2, "usage: %s [-f file] [-v] [arg ...]\n", argv0);
    exits("usage");
}

void
main(int argc, char **argv)
{
    int i;

    ARGBEGIN {
    case 'f':
        file = EARGF(usage());
        break;
    case 'v':
        debug++;
        break;
    default:
        usage();
    } ARGEND

    print("debug: %d\n", debug);
    print("file: %s\n", file);
    for(i = 0; i < argc; i++)
        print("argv[%d]: %s\n", i, argv[i]);
    exits(nil);
}

特筆すべきところは、ARGENDを抜けた後のargv[0]は、コマンド名ではなくオプションを除いた最初の引数を参照している。上記コードを実行結果は以下のようになる。

% opts -f file -v a b c
debug: 1
file: file
argv[0]: a
argv[1]: b
argv[2]: c

% opts -f # 引数が足りない場合
usage: opts [-f file] [-v] [arg ...]

ロングオプションは対応していないけれど、Plan 9のコマンドはオプションがあまりないので、これで充分なんだろうと思う。cat(1)はオプションが無いし、cp(1)は3個、ls(1)でも12個しかない。

usage: ls [-dlmnpqrstuFQ] [file ...]

usage: cp [-gux] fromfile tofile

サブコマンド

Plan 9の標準シェルであるrc(1)は、$path配下のディレクトリも検索対象となるので、

% fossil/conf -w file /dev/sd01/fossil
% auth/factotum
% upas/fs

のように関連するコマンドをディレクトリでまとめることが普通となっていて、サブコマンドが使われることはほとんどない*2が、ARGBEGINマクロを使ってサブコマンドを実装できるので参考程度に書いておく。単純に、サブコマンド関数の引数がargcargvになっていればARGBEGINマクロを利用できる。

#include <u.h>
#include <libc.h>

void cmdinit(int, char **);
void cmdprint(int, char **);

struct {
    char *name;
    void (*cmd)(int, char **);
} cmds[] = {
    { "init", cmdinit },
    { "print", cmdprint },
};

int debug;
char *file;

void
usage(void)
{
    fprint(2, "usage:\t%s init [-y]\n", argv0);
    fprint(2, "\t%s print\n", argv0);
    exits("usage");
}

void
main(int argc, char **argv)
{
    int i;

    ARGBEGIN {
    case 'f':
        file = EARGF(usage());
        break;
    case 'v':
        debug++;
        break;
    default:
        usage();
    } ARGEND
    if(argc == 0)
        usage();

    for(i = 0; i < nelem(cmds); i++)
        if(strcmp(cmds[i].name, argv[0]) == 0){
            cmds[i].cmd(argc, argv);
            exits(nil);
        }
    usage();
}

void
cmdinit(int argc, char **argv)
{
    int yes;

    yes = 0;
    ARGBEGIN {
    case 'y':
        yes++;
        break;
    default:
        usage();
    } ARGEND
    print("init: debug=%d yes=%d\n", debug, yes);
}

void
cmdprint(int argc, char **argv)
{
    ARGBEGIN {
    default:
        usage();
    } ARGEND
    print("print: file=%s\n", file);
}

*1:以前は16bitだったけど今は22bitのはず

*2:OS標準のコマンドでは全く見た記憶がない