東大の演習問題に挑戦
東大のアルゴリズムとデータ構造演習(リンク切れ)が面白そうなので演習問題を解いてみる。
課題1-A:シェルを実装せよ。
ガンバリマス。
方針
パイプもリダイレクトも無し。一番簡単そうな実装を目指します。
- 1行取得は面倒なのでGNU getline使います。
- fork,exec,waitを使って実装します。
- execはexecvpを使うことにしました。
- 引数の処理は、単語の先頭ポインタをargvに追加して、スペースか改行があったら\0で踏み潰していきます。
コーディング
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> void prompt(void) { printf("* "); } int main(void) { /* 行の取得用 */ char *line = NULL; size_t len = 0; ssize_t length; char *p; /* execに渡すやつ */ char *argv[256]; int argc; prompt(); while ((length = getline(&line, &len, stdin)) != -1) { if (length > 1) { /* 改行のみの場合は無視 */ argc = 0; argv[argc++] = line; /* 踏み潰しながらargvを作る */ for (p = line; *p != '\0'; p++) { if (*p == ' ') { *p = '\0'; argv[argc++] = p + 1; } else if (*p == '\n') { *p = '\0'; argv[argc] = NULL; /* 改行区切り */ } } if (fork() == 0) { /* 子プロセス */ execvp(line, argv); /* エラーの時はここへ */ fprintf(stderr, "起動できまっせん。\n"); exit(EXIT_FAILURE); } wait(NULL); } prompt(); } if (line) free(line); return EXIT_SUCCESS; }
できたぁ〜。
プロンプトは「*」っす。
動かしてみる
% ./shell * head -5 /etc/passwd root:x:0:0:root:/root:/bin/zsh bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin *
うは、動いてるよ!!
反省とか
- 引数の処理がイマイチ。引数の処理を練り直したい。
- 踏み潰し方だけではあんまり良くない。単語を区切るだけだけど、適当に構文解析したほうがいいかも。
- パイプ、リダイレクトを実装してみたい。
- ワイルドカードがホシィ。
シェル作るのは楽しいっす。
イマイチなので、パーサーを書いたバージョン
やっぱり、引数の処理が気に入らないので、パーサー書いた。
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> void prompt(void) { printf("* "); } /* パーサ */ void perse(char *p, char *argv[]) { int argc = 0; for (;;) { while (*p == ' ' || *p == '\t') { /* スペースは踏み潰しながらトバス */ *p = '\0'; p++; } if (*p == '\n' || *p == '\0') { /* 改行ならしゅーりょー */ *p = '\0'; argv[argc] = NULL; return; } argv[argc++] = p; /* 単語の先頭ポインタを保存 */ while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0') { /* 単語らしきものを探す */ p++; } } } int main(void) { /* 行の取得用 */ char *line = NULL; size_t len = 0; /* execに渡すやつ */ char *argv[256]; prompt(); while (getline(&line, &len, stdin) != -1) { perse(line, argv); /* コマンドの解析 */ if (argv[0] != NULL) { /* 引数チェック */ if (fork() == 0) { /* 子プロセス */ execvp(argv[0], argv); /* エラーの時はここへ */ fprintf(stderr, "起動できまっせん。\n"); exit(EXIT_FAILURE); } else { /* 親父 */ wait(NULL); } } prompt(); } if (line) free(line); return EXIT_SUCCESS; }
うん。スッキリ。これでビルトインコマンドが書けそうだ。
おっと、バグがあったのでちょっと修正。
更新履歴
- 2013/02/09
- 元ネタのリンクが切れてますね。残念。