回答例(一部)

東京工業大学 工学部 情報工学科
プログラミング第三
演習2の回答例 (一部)
東京工業大学 大学院情報理工学研究科
小林隆志
野呂智哉
<[email protected]>
課題1

プログラミング第三のホームページから
ex1.c をダウンロードし,ex1.c に変更を加える
/* ex1.c */
void child() {
printf("PID = %d, PPID = %d¥n", getpid(), getppid());
sleep(1);
printf("Process %d terminated.¥n", getpid());
exit(0);
}
int main()
{
printf("PID = %d, PPID = %d¥n", getpid(), getppid());
/*子プロセスを生成し,次の child() を子プロセスで実⾏するよう変更*/
child();
/*ここで親プロセスが生成した子プロセスの終了を待つようにする*/
printf("Process %d terminated.¥n", getpid());
return 0;
}
2
1
課題2-1
●
●
未完成シェルプログラム ex2.c をダウンロード
–
ex2.c はコマンドの⼊⼒は受け付ける
–
コマンドの実⾏はできない
–
Ctrl-d(ctrl キーと d キーの同時押し)で終了する
以下を実装
●
ユーザが⼊⼒したコマンドを実⾏できるようにする
●
“exit” が⼊⼒された場合はシェルプログラム⾃体が終了する
(条件)
●
forkとexecファミリで実装してください
●
system()関数による実装は減点します。
3
課題2-2
• パイプ機能を加える
– 例: ls | wc -l
ls:
現在作業中のディレクトリにあるファイルの一覧を
標準出力に出力する
wc -l : 標準入力中の行数を数える
つまり,上記をパイプで接続すると,現在作業中の
ディレクトリにあるファイルの数を数える
– “comm1 | comm2” と一段のパイプが扱えれば OK
– “|” の両側にスペースがあると仮定してよい
4
2
課題2-3 ※ 余力のある人向け
さらに一般的なshellにある機能を加えて下さい.
例1) “comm1 | comm2 | ... | commN” という
多段のパイプを扱えるように拡張する
例2) cd コマンドを利⽤できるようにする
例3) ヒストリ機能*を利⽤できるようにする
*ヒストリ機能: 過去に実⾏したコマンドを呼び出す機能.「↑」や「↓」キーで
ヒストリの探索を⾏った経験があると思うが,例えば bash というシェルプログラムでは
!!
と⼊⼒すれば直前に実⾏したコマンドがもう一度実⾏される.また
history
と⼊⼒すればヒストリのリストが表⽰され,
!50
とすればリスト中の50番のコマンドが実⾏されます.
5
提出状況と回答状況

第2回 E 45人,O 50人
問題1:全員 回答
 問題2:2-1が未完成 E 3名,O 7名
2-2が未完成 E 24名,O 20名
 問題3: 多段パイプ (5/16), cd (4/8),
ヒストリ(3/8), リダイレクト(1/0)

6
3
実装例 問題1
int main()
{
printf("PID = %d, PPID = %d¥n", getpid(), getppid());
int status;
pid_t pid = fork();
if (pid == -1) {
perror("error in fork");
exit(1);
} else if (pid == 0) {
child();
} else {
wait(&status);
}
printf("Process %d terminated.¥n", getpid());
return 0;
}
7
実装例 問題2-1
If (argc > 0) {
if (strcmp("exit", argv[0]) == 0) {
printf("Goodbye!¥n");
exit(0);
}
子プロセスの生成に失敗した場合
int status;
pid_t pid = fork();
if (pid == -1) {
execvpはパス検索を行うので,
perror("Error in fork");
第1引数にフルパスを指定する必要はない
} else if (pid == 0) {
execvp(argv[0], argv);
perror("Failed");
exit(1);
execvpが正常に実行されれば
} else {
コマンド終了時に子プロセスは自動的に終了するが,
wait(&status);
}
execvpの実行に失敗した場合
}
(入力したコマンドが存在しない場合)
子プロセスを明示的に終了させなければならない
8
4
実装例 問題2-2
int pos_pipe;
for (pos_pipe = 0; pos_pipe < argc; pos_pipe++) {
if (strcmp("|", argv[pos_pipe]) == 0) {
argv[pos_pipe] = NULL; break;
}
パイプ位置検出,NULLにしておく
}
if (pos_pipe == argc) {
/* パイプを含まない⼊⼒ (問題2-1) */
} else {
int fd[2]; pipe(fd);
9
}
パイプ作成
pid_t pid = fork();
if (pid == -1) {
perror("Error in fork"); exit(1);
1つ目のコマンド
} else if (pid == 0) {
dup2(fd[1], 1); close(fd[1]);
execvp(argv[0], argv);
perror("Failed"); close(fd[0]); exit(1);
} else {
close(fd[1]);
wait(&status);
if (status != 0) {close(fd[0]); continue;}
pid_t pid2 = fork();
if (pid2 == -1) {
子プロセスを2つ生成しているので,
perror("Error in fork"); exit(1); waitは2回必要
} else if (pid2 == 0) {
dup2(fd[0], 0); close(fd[0]);
execvp(argv[pos_pipe+1], &argv[pos_pipe+1]);
perror("Failed"); exit(1);
} else {close(fd[0]); wait(&status);}
}
2つ目のコマンド
実装例 問題2-3 (多段パイプ)
10
int fd[2], fd_backup;
int i, command_pos = 0;
for (i = 0; i < argc; i++) {
if (strcmp("|", argv[i]) != 0) continue;
argv[i] = NULL;
if (command_pos > 0) {
if ((fd_backup = dup(fd[0])) == -1) {
1つ前のコマンドの出力が
perror("Error in dup"); exit(1);
fd[0]に入っているので,
}
close(fd[0]);
fd_backupにとっておく
}
pipe(fd);
pid_t pid = fork();
if (pid == -1) {
perror("Error in fork"); exit(1);
} else if (pid > 0) {
if (command_pos > 0) close(fd_backup);
close(fd[1]); wait(&status); command_pos = i + 1;
} else {
if (command_pos > 0) {
1つ前のコマンドの出力を
if (dup2(fd_backup, 0) == -1) {
perror("Error in dup2"); exit(1);
標準入力に
}
close(fd_backup);
標準出力をfd[1]に
}
if (dup2(fd[1], 1) == -1) {
perror("Error in dup2"); exit(1);
argv[command_pos]から
}
argv[i]までのコマンドを実行
close(fd[1]);
execvp(argv[command_pos], &argv[command_pos]);
perror("Failed"); close(fd[0]);exit(1);
}
}
(次ページに続く)
5
実装例 問題2-3 (多段パイプ) (つづき)
最後のコマンドを実行
argv[command_pos]から末尾まで
if (command_pos > 0) {
if ((fd_backup = dup(fd[0])) == -1) {
perror("Error in dup"); exit(1);
}
close(fd[0]);
}
pid_t pid = fork();
if (pid == -1) {
perror("Error in fork"); exit(1);
} else if (pid > 0) {
if (command_pos > 0) close(fd_backup);
wait(&status);
} else {
if (command_pos > 0) {
if (dup2(fd_backup, 0) == -1) {
perror("Error in dup2"); exit(1);
}
close(fd_backup);
}
execvp(argv[command_pos], &argv[command_pos]);
perror("Failed"); exit(1);
}
11
実装例 問題2-3 (cd)
if (strcmp(argv[0], "cd") == 0) {
if (argc == 1) argv[1] = getenv("HOME");
chdir(argv[1]);
}
引数がない場合はホームディレクトリ
12
6
実装例 問題2-3 (ヒストリ)
char *history[1000];
int last = 0;
先に履歴を保存しておく場所を用意しておく
if ('!' == argv[0][0]) {
int index = last;
if (strcmp("!!", argv[0]) != 0) {
char *command = &argv[0][1];
index = atoi(command);
}
/* inputの内容をhistory[index]にして再実⾏ (省略) */
} else if (strcmp("history", argv[0]) == 0) {
/* historyの内容を出⼒ (省略) */
}
/* inputの内容を実⾏した後,
inputをhistory[last]に追加,last++ (省略) */
13
7