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-8はPlan 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マクロを使ってサブコマンドを実装できるので参考程度に書いておく。単純に、サブコマンド関数の引数がargcとargvになっていれば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); }