ページャ
ページフォルトの話に続くと誤解されそうだが、今回はmore、pg、lessなどファイルページャの方の話。近所の図書館でO'Reillyの「UNIX Cプログラミング(Using C on the UNIX System)」を見つけた*1。今日のネタはここから。
ファイルを特定の行数でぶった切るだけの、素のページャを書いてみる。教科書的には、ioctl経由でttyドライバを制御する例になっている。本書の発行は1991年なのだが(ターゲットは4.2BSD、4.3BSD、SVR2あたり)、ioctl直叩きはいまどきのUNIXじゃ動かないだろうから*2、termiosを使って書き直してみる。やっていることはファイルを22行表示して、端末をエコーオフ、非カノニカルモード化して入力を待つ、ということの繰り返し。main関数の先頭でモードを設定し、終了前に元の設定に戻している。
#include <stdio.h> #include <termios.h> #define LINES 22 void prompt() { char answer; printf(":"); answer = getchar(); putchar('\n'); } void more(char *file) { FILE *fp; int line; char linebuf[1024]; if ((fp = fopen(file, "r")) == NULL) { perror(file); return; } for (;;) { line = 1; while (line < LINES) { if (fgets(linebuf, sizeof(linebuf), fp) == NULL) { fclose(fp); prompt(); return; } fputs(linebuf, stdout); line++; } prompt(); } } int main(int argc, char **argv) { struct termios tio, tin; if (argc < 2) { fprintf(stderr, "Usage: %s file [file...]\n", *argv); return 1; } tcgetattr(0, &tio); tin = tio; tin.c_lflag &= ~ECHO; tin.c_lflag &= ~ICANON; tin.c_cc[VMIN] = 1; tin.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &tin); while (--argc) more(*++argv); tcsetattr(0, TCSANOW, &tio); return 0; }
Plan9にはmoreやlessはなくて、p(ageinate)。でも、pってリターンキーを押さないと次ページに進まないし、rawモードになっていない雰囲気。もう少しmoreっぽく変更しようと思ったが、そもそもPlan 9のコンソールはttyとずいぶん考え方が違う。ということで、入力待ちの処理はauth/login.cのreadln関数を参考にした。コンソール(/dev/cons)をrawモードに設定するなどの制御は/dev/consctlに書き込めばよい。ちょっと冗長になるけど、実装がブラックボックスになっている感じがなくてグッド。
#include <u.h> #include <libc.h> #include <bio.h> #define LINES 22 Biobuf bout; void prompt(void) { char ch; int fdin, fdout, n; fdin = open("/dev/cons", OREAD); fdout = open("/dev/cons", OWRITE); fprint(fdout, "%s", ":"); for (;;) { n = read(fdin, &ch, 1); if (n < 0) { close(fdin); close(fdout); fprint(2, "cannot read cons"); exits("prompt"); } if (ch == 0x7f) exits(0); else { write(fdout, "\n", 1); close(fdin); close(fdout); return; } } } void more(char *file) { Biobuf bin; char *s; int line; int f, n; f = open(file, OREAD); if (f < 0) { fprint(2, "cannot open %r\n"); return; } Binit(&bin, f, OREAD); for (;;) { line = 1; while (line < LINES) { s = Brdline(&bin, '\n'); if (s == 0) { prompt(); return; } Bwrite(&bout, s, Blinelen(&bin)); Bflush(&bout); line++; } prompt(); } Bterm(&bin); } void main(int argc, char **argv) { int ctl; if (argc < 2) { fprint(2, "Usage: %s file [file...]\n", *argv); exits("no files"); } ctl = open("/dev/consctl", OWRITE); if (ctl < 0) { fprint(2, "cannot set raw mode"); exits("prompt"); } write(ctl, "rawon", 5); Binit(&bout, 1, OWRITE); while (--argc) more(*++argv); Bterm(&bout); write(ctl, "rawoff", 6); close(ctl); exits(0); }
以下、余談。コンソールのデフォルトモードはcookedモードで、このモードでは、プロセスのread前に、カーソルキーが入力されたらどうするとか、Escでホールドモードとかいった特定の文字に対するルール(ラインディシプリン)にしたがって入力が解釈される。一方、入力をそのまま欲しい場合はrawモードに変更する必要がある。rawモードでは、readはバッファリングされずに一文字ずつ渡される。Plan9の場合は前述したように/dev/consctlに"rawon"を書き込んでやればよい。UNIXの場合はOS依存でICANONとかCBREAKとかRAWとか、黒魔術の世界が広がっている。
ネタ元にしておいて何だが、いまさらこの本でUNIX + Cを勉強する価値はないだろうな。「ふつうのLinuxプログラミング」とか、もっと現代的な話題を扱ったよい本がいろいろ出ているし。
UNIX Cプログラミング (NUTSSHELL HANDBOOKS)
- 作者: デビッド・A.クリ,アスキー書籍編集部
- 出版社/メーカー: アスキー
- 発売日: 1991/07
- メディア: 単行本
- 購入: 2人 クリック: 16回
- この商品を含むブログ (12件) を見る
ふつうのLinuxプログラミング Linuxの仕組みから学べるgccプログラミングの王道
- 作者: 青木峰郎
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2005/07/27
- メディア: 単行本
- 購入: 35人 クリック: 450回
- この商品を含むブログ (150件) を見る