質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

Q&A

4回答

1408閲覧

【C言語】シェルでリダイレクトを実装したいがプロセスが無限に作成されるエラーが起きる

ijuya_yika

総合スコア50

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Linux

Linuxは、Unixをベースにして開発されたオペレーティングシステムです。日本では「リナックス」と呼ばれています。 主にWebサーバやDNSサーバ、イントラネットなどのサーバ用OSとして利用されています。 上位500のスーパーコンピュータの90%以上はLinuxを使用しています。 携帯端末用のプラットフォームAndroidは、Linuxカーネル上に構築されています。

0グッド

0クリップ

投稿2018/03/19 20:41

編集2018/03/20 18:11

前提・実現したいこと

自作シェルでリダイレクト(>>)を実装したいのですが
以下のコードを実行すると無限ループに陥ってしまいます。
このコードのどこが無限ループの原因となっているのか教えていただきたいです。


【追記1】
指摘を受けて:

getCommandの他、省略していた関数やデバッグ文を記述しました。

リダイレクトのみ機能していない状態だったので該当のコードのみ載せたのですが誤解を招いてしまいすみません。

以下のコードをコンパイルして動かすとパイプ、リダイレクトを含まないコマンド及びパイプコマンドは問題なく動くのですが、リダイレクトの場合にプロセスが無限に作られてしまいCTRL+C等でプログラムを終了できない状態になってしまいます。


【追記2】
239行目にexit(1)を追加した所正常に動くようになりました。なぜここにexit(1)がないと無限プロセス作成エラーになるのか教えていただける方がいたらお願いしたいです。

該当のソースコード

C

1 2#include <stdio.h> 3#include <stdlib.h> 4#include <unistd.h> 5#include <string.h> 6#include <errno.h> 7#include <sys/wait.h> 8#include <sys/stat.h> 9#include <sys/types.h> 10#include <fcntl.h> 11#define MAX_ARG 50 12#define READ 0 13#define WRITE 1 14 15/* @戻り値 0 => 普通のコマンド, 1 => パイプコマンド, 2 => リダイレクトコマンド */ 16void Type(char* command, int* type, int* splitPosition){ 17 int pos = 0; 18 19 (*type) = 0; 20 21 while(command[pos] != '\0'){ 22 23 if(command[pos] == '|'){ 24 (*type) = 1; 25 (*splitPosition) = pos; 26 } 27 else if(command[pos] == '>' && command[pos+1] == '>'){ 28 (*type) = 2; 29 (*splitPosition) = pos; 30 } 31 32 pos++; 33 } 34} 35 36int substr(char* S1, char* S2, int P, int L){ 37 if( P < 0 || L < 0 || L > strlen(S1) ){ 38 return -1; 39 } 40 41 for( S1 += P; *S1 != '\0' && L > 0; L--){ 42 *S2++ = *S1++; 43 } 44 45 *S2 = '\0'; 46 47 return 0; 48} 49 50 51void split(char* command, char* part1, char* part2, int* type){ 52 int splitPosition = 0; 53 54 Type(command, type, &splitPosition); 55 56 #ifndef DEBUG 57 printf("splitPosition = %d\n", splitPosition); 58 #endif 59 60 if( (*type) != 0 ){ 61 int start = ( (*type) == 1 )? splitPosition + 1 : splitPosition + 2; /* 修正:>> or | の前後にスペース無しに対応 */ 62 int len = strlen(command) - start; 63 64 if( substr(command, part1, 0, splitPosition) < 0 || 65 substr(command, part2, start, len) < 0){ 66 perror("substr\n"); 67 exit(1); 68 } 69 } else { 70 strcpy(part1, command); 71 } 72 73 #ifndef DEBUG 74 printf("part1 = %s\n", part1); 75 printf("part2 = %s\n", part2); 76 #endif 77} 78 79void getCommand(char* command){ 80 81 scanf("%255[^\n]%*[^\n]", command); 82 scanf("%*c"); 83 84 #ifndef DEBUG 85 printf("command = %s\n", command); 86 #endif 87} 88 89 90int to_token(char* command, char** tokens){ 91 int argc = 0; 92 93 tokens[argc] = strtok(command, " "); 94 95 while(argc < MAX_ARG){ 96 tokens[++argc] = strtok(NULL, " "); 97 98 if(tokens[argc] == NULL){ 99 break; 100 } 101 } 102 103 return argc; 104} 105 106 107int Parse(char* command, char** parsedCommand){ 108 int argc = to_token(command, parsedCommand); 109 int ret; 110 111 if(strcmp( parsedCommand[argc - 1], "&") == 0){ 112 parsedCommand[argc - 1] = NULL; 113 ret = 1; 114 } else { 115 ret = 0; 116 } 117 118 return ret; 119} 120 121 122void RegCmd(char* part1){ 123 char* parsedCommand[20]; 124 int background; 125 pid_t pid; 126 127 background = Parse(part1, parsedCommand); 128 129 if( (pid = fork()) == 0){ 130 execvp(parsedCommand[0], parsedCommand); 131 perror("execvp failed\n"); 132 exit(1); 133 } else { 134 if(!background){ 135 waitpid(pid, NULL, 0); 136 } 137 } 138} 139 140void PipeCmd(char* part1, char* part2){ 141 char* cmd_left[20]; 142 char* cmd_right[20]; 143 int fds[2]; 144 pid_t pid1, pid2; 145 int background; 146 147 if( pipe(fds) < 0){ 148 perror("pipe failed\n"); 149 exit(1); 150 } 151 152 153 background = Parse(part1, cmd_left) || 154 Parse(part2, cmd_right); 155 156 if( (pid1 = fork()) < 0){ 157 perror("fork failed\n"); 158 exit(1); 159 } 160 161 if( pid1 == 0 ){ 162 dup2(fds[READ], 0); 163 close(fds[WRITE]); 164 execvp(cmd_right[0], cmd_right); 165 perror("execvp failed\n"); 166 } 167 168 if( (pid2 = fork()) < 0){ 169 perror("fork failed\n"); 170 exit(1); 171 } 172 173 if( pid2 == 0 ){ 174 dup2(fds[WRITE], 1); 175 close(fds[READ]); 176 execvp(cmd_left[0], cmd_left); 177 perror("execvp failed\n"); 178 } 179 else { 180 close(fds[0]); 181 close(fds[1]); 182 183 if(!background){ 184 waitpid(pid1, NULL, 0); 185 waitpid(pid2, NULL, 0); 186 } 187 } 188} 189 190void RedirectCmd(char* part1, char* part2){ 191 char* cmd_left[20]; 192 char* cmd_right[20]; 193 int fds[2]; 194 int fd; 195 int count; 196 pid_t pid1, pid2; 197 int background; 198 char c; 199 200 if( pipe(fds) < 0){ 201 perror("pipe failed\n"); 202 exit(1); 203 } 204 205 206 background = Parse(part1, cmd_left) || 207 Parse(part2, cmd_right); 208 209 210 if( (pid1 = fork()) < 0){ 211 perror("fork failed\n"); 212 exit(1); 213 } 214 215 if( pid1 == 0 ){ 216 dup2(fds[WRITE], 1); 217 close(fds[READ]); 218 execvp(cmd_left[0], cmd_left); 219 perror("execvp failed\n"); 220 exit(1); 221 } 222 223 if( (pid2 = fork()) < 0){ 224 perror("fork failed\n"); 225 exit(1); 226 } 227 228 if( pid2 == 0 ){ 229 if( (fd = open(cmd_right[0], O_RDWR | O_CREAT | O_APPEND, 0600)) < 0){ 230 perror("open failed\n"); 231 close(fd); 232 exit(1); 233 } 234 235 dup2(fds[READ], 0); 236 close(fds[WRITE]); 237 238 while( (count = read(0, &c, 1)) > 0){ 239 write(fd, &c, 1); 240 } 241 242 close(fd); 243 244 /**** 追記2 : exit(1)がないと無限プロセスになるため追加 ****/ 245 exit(1); 246 } 247 else { 248 close(fds[READ]); 249 close(fds[WRITE]); 250 waitpid(pid1, NULL, 0); 251 waitpid(pid2, NULL, 0); 252 } 253 254} 255 256int main(void){ 257 char cmd[256]; 258 char part1[128]; 259 char part2[128]; 260 int type = -1; 261 262 printf("\n> "); 263 getCommand(cmd); 264 265 while( (strcmp(cmd, "quit")!=0) && 266 (strcmp(cmd, "exit")!=0) ){ 267 split(cmd, part1, part2, &type); 268 269 if(type == 0){ 270 RegCmd(part1); 271 } 272 else if(type == 1){ 273 PipeCmd(part1, part2); 274 } 275 else if(type == 2){ 276 RedirectCmd(part1, part2); 277 } 278 279 #ifndef DEBUG 280 printf("\nType = %d", type); 281 #endif 282 283 printf("\n> "); 284 getCommand(cmd); 285 } 286 return 0; 287} 288 289

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kazto

2018/03/19 21:42

getCommand(cmd)関数の内容を貼り付けて下さい。
cateye

2018/03/19 22:12 編集

getCommand()ってWindows PowerShellでしょうか? ・・・違うなら、getCommand()の中身等情報不足です。
a_saitoh

2018/03/19 22:45

「無限ループになってしまう」のは一体どういう根拠ですか?観察出来る挙動そのものを書いてください。デバッガで追いかけると無限ループしてるのか、待っても終わらないので無限ループだとおもった、のか。
ijuya_yika

2018/03/20 03:02 編集

ご指摘ありがとうございます、コード修正しました。また無限ループ => 無限にプロセスが作成されてしまうに変更しました。
a_saitoh

2018/03/22 07:26

本当にプロセス一覧を見てプロセス無限生成と確認しましたか?本当にそうなら最悪PCが操作不能になってしまいそうなものですが。
guest

回答4

0

自作シェルを作るというのはきっと色々勉強になると思います。

しかしながら自分でデバッグしないのでは何にもなりません。

デバッグしたならそれがご質問に書かれているはずですが、全然書かれてないので「デバッグしてないのだろう」と判断しました。デバッグしてください。


最も基本的な誰でもが最初にやるデバッグは「デバッグプリント」だと思います。
getCommandの結果はcmd, p1, p2に期待どおり設定されているか?
execvpには正しい文字列が渡っているか?
whileループの部分で、無限にループしているのか、ループの途中でreadで止まっているのか、読めている文字は何か、等々。

こういうのはデバッグプリントをちょこっと入れてやると「事実」がつかめます。ソースを見てもわからないときは「どう動いているかの事実」を掴んでそこから逆に原因を探りましょう。


無限ループになっている

と書いておられますが本当に無限ループになっていますか?コマンドプロンプトが表示されないためそれを「無限ループ」と言っているだけではないのですか?

「プログラムが反応しない」ことを「無限ループ」とは言いません。例えば「次のプロンプトが表示されない」というべきです。本当に無限ループしているのかも知れませんが、単にreadなどのシステムコールが戻ってきてないだけかも知れません。

プログラマーというのはこういう「一見細かい違いに見える」言葉にも非常に敏感です。用語が違うと認識がずれ、それがバグを作り込んだりバグの原因を正しく把握できない原因になるからだろうと思います。

投稿2018/03/20 00:45

KSwordOfHaste

総合スコア18394

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ijuya_yika

2018/03/20 03:07

コメントありがとうございます。質問及びコードが大雑把すぎたので修正しました。execvp等に入る文字列が正しいことはデバッグコードで確認済です。
KSwordOfHaste

2018/03/20 03:24

質問文を追加し大分論点がはっきりしてきたと思います。ただ・・・ part2が空文字列になってしまってます。これは期待ではないように思いますが?
ijuya_yika

2018/03/20 03:29 編集

>> が入力されない場合はpart2は空、入力された場合はpart2に>>の右側にファイル名が入り、main関数のif文で>>が入力された時とされない時で処理を分岐してますけど、part2が空の場合なにか問題ありますか?
KSwordOfHaste

2018/03/20 03:49 編集

ls>>f とやるとpart1が"ls",part2が"f"になるのが期待なんじゃないかなぁと思ったのです。実際はこのケースでpart2が""になるように思います。論理を詳しくみてませんが・・・ --- >>の両側にスペース文字が必要なんですね。これは本件とは関係なかったですね。
KSwordOfHaste

2018/03/20 04:16 編集

exitしない原因を探るヒントですが、プロンプトを表示するところで printf("\n%d> ", getpid()); とやってみると核心にせまれると思います。 ---追記--- ちょっと中途半端なコメントでした。「各デバッグプリントにgetpidの値も表示すると核心にせまれる」と言った方がよかったです。
ijuya_yika

2018/03/20 03:49

コメントありがとうございます。 >> の左右にスペースが入ることを想定したコードだったのでスペース無しに対応するよう60行目を修正しました。
guest

0

根本的問題として、リダイレクトに誤解があるようです。

cmd>>file

の実装が

cmd |cat>file

的なものになってます。
リダイレクトを行うためのforkは必要ありません。

あと、

file ならO_TRUNC が要りますし

file なら O_APPEND

投稿2018/03/19 22:48

a_saitoh

総合スコア702

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ijuya_yika

2018/03/20 18:09

コメントありがとうございます。O_APPENDが抜けていましたので修正しました。fork無しの実装、差し支えなければ簡単な手順を訊いても良いでしょうか
a_saitoh

2018/03/22 03:20

パイプがない場合はだいたいこんな感じ。 pid=fork(); if(pid==0){   fd=open(filename,O_WRONLY|O_TRUNC|O_CREAT)   fdを1に繋ぎ変えてfdをclose   exec(・・・)   エラー処理&exit }
guest

0

while( (count = read(0, &c, 1)) > 0){ write(fd, &c, 1); }

readでパイプを読み出すときにブロックされているのでは?なので親プロセスのwaitpid(pid2, NULL, 0);で返ってこない。

本質的には下記だけで十分だと思います。(嘘言ってたらごめんなさい。)

fd = open(Right[0], O_WRONLY|O_CREAT|O_APPEND, 0666); dup2(fd, 1); close(fd); execvp(Left[0], Left);

投稿2018/03/20 04:19

nullbot

総合スコア910

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nullbot

2018/03/21 05:55

おお、全く原因が違いましたね。お恥ずかしい。。。 なるほど。pid1で生成された子プロセスがpid2を子プロセスとして生成するので・・・という理由ですかね。さらに子プロセスをexitで終了させてなければmain関数のwhileループに戻り、子プロセスの方は標準入力がdupで差し替えられているので、getCommand内のscanfが期待通りには動作しないと。
guest

0

while( (count = read(0, &c, 1)) > 0){

これが意味不明ですね


fd = open(Right[0], O_RDWR | O_CREAT, 0600)) < 0);

Right に何も入ってない


その他、このソースに提示されてない項目がたくさんありそーですね。

投稿2018/03/19 22:05

編集2018/03/19 22:48
y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

cateye

2018/03/19 22:25 編集

NULLポインタを渡してるように見えますが・・・なんでしょうね? ファイルディスクリプタの番号、STDIN_FILENOが0なのでそうしてるように見えます。
y_waiwai

2018/03/20 03:13

いつのまにか質問が変わってるw
ijuya_yika

2018/03/20 03:14

すみません、質問が大雑把すぎて自分で読み返しても?だったのでかなり変更しました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問