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

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

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

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

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

Q&A

解決済

2回答

1737閲覧

C言語:fgetsによるファイル読み取りが上手くいかない

SUNMOON_14

総合スコア20

C

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

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

1グッド

0クリップ

投稿2021/07/20 11:33

編集2021/07/20 13:05

プログラム

簡易の自作シェルを作成しようとしています。作成途中のプログラム(以下、「本プログラム」という。)から切り出してきたもの(以下、「小プログラム」という。)を以下に示します。なお、以下の小プログラムも実行しましたが、本プログラムと全く同じ出力を得ており、同一視できるものと考えます。

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <sys/types.h> 4#include <unistd.h> 5#include <sys/wait.h> 6#include <string.h> 7 8#define BUFLEN 1024 9#define MAXARGNUM 256 10 11void execute_command(char *[], int); 12int parse(char [], char *[]); 13 14int main() 15{ 16 char command_buffer[BUFLEN]; // コマンド用のバッファ 17 char *args[MAXARGNUM]; // 引数へのポインタの配列 18 int command_status; // コマンドの状態を表す 19 20 for(;;){ 21 printf("Command: "); // プロンプトを表示 22 23 if(fgets(command_buffer, BUFLEN, stdin) == NULL){ 24 printf("\n"); 25 exit(0); 26 } 27 printf("buf = %s", command_buffer);// debug 28 29 /* 入力されたバッファ内のコマンドを解析 */ 30 command_status = parse(command_buffer, args); 31 32 for(int i = 0; args[i] != NULL; ++i){ 33 printf("args[%d] = [%s]\n", i, args[i]); 34 } // debug 35 36 if(command_status == 2){ 37 printf("done.\n"); 38 exit(EXIT_SUCCESS); 39 } else if(command_status == 3){ 40 continue; 41 } 42 43 execute_command(args, command_status); // コマンドを実行 44 } 45 return 0; 46} 47 48int parse(char buffer[], char *args[]) 49{ 50 int arg_index = 0; // 引数用のインデックス 51 int status = 0; // コマンドの状態を表す 52 53 /* バッファ内の最後にある改行をヌル文字へ変更 */ 54 char *p; 55 p = buffer + (strlen(buffer) - 1); 56 if(*p == '\n'){ 57 *p = '\0'; 58 } 59 60 if(strcmp(buffer, "exit") == 0){ 61 status = 2; 62 return status; 63 } 64 65 while(*buffer != '\0'){ 66 while(*buffer == ' ' || *buffer == '\t'){ 67 *(buffer++) = '\0'; 68 } 69 /* 空白の後が終端文字であればループを抜ける */ 70 if(*buffer == '\0'){ 71 break; 72 } 73 args[arg_index] = buffer; 74 ++arg_index; 75 while((*buffer != '\0') && (*buffer != ' ') && (*buffer != '\t')){ 76 ++buffer; 77 } 78 } 79 80 /* 最後の引数の次にはヌルへのポインタを格納する */ 81 args[arg_index] = NULL; 82 83 if(arg_index > 0 && strcmp(args[arg_index - 1], "&") == 0){ 84 --arg_index; 85 args[arg_index] = '\0'; 86 status = 1; 87 } 88 else{ 89 status = 0; 90 } 91 92 /* 引数が何もなかった場合 */ 93 if(arg_index == 0) { 94 status = 3; 95 } 96 97 /* コマンドの状態を返す*/ 98 return status; 99} 100 101void execute_command(char *args[], int command_status) 102{ 103 int pid; // プロセスID 104 int status; // 子プロセスの終了ステータス 105 106 switch(pid = fork()){ 107 case 0: 108 //子プロセス 109 exit(1); 110 case -1: 111 perror("fork"); 112 exit(1); 113 default: 114 if(waitpid(pid, &status, 0) < 0){ 115 perror("waitpid"); 116 exit(1); 117 } 118 } 119 return; 120}

プログラムの補足

関数parseは、command_bufferに格納された文字列を引数ごとに分解するのが主たる働きです。
command_buffer = ls -l
の場合、
args[0] = ls
args[1] = -l      <-文末の改行コードは取り除かれます
args[2] = NULL
が格納されます。

状況

まず、test.txtを以下とします:

text

1cd 2history (←改行なし)

シェルで「./a.out < test.txt」を実行すると、次のような出力となります。

./test < text Command: buf = cd args[0] = [cd] Command: buf = historyhistoryargs[0] = [historyhistory] Command:

本来は、以下の出力を期待しています。

Command: buf = cd args[0] = [cd] Command: buf = historyargs[0] = [history] Command:

今わかっていること

プログラム43行目:execute_commandの実行をコメントアウトすると期待する出力をします。
つまり、execute_commandが原因だと思うのですが、全く見当がつきません。

誤りを指摘いただければと思います。

thkana👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

109行目の exit(1);_exit(1); に変えると正常になりますでしょうか?
もしこれで解消するようであれば、以下の理由のどちらかによるものだと思います。

** ※実装依存の部分なので、お使いの環境によって挙動が異なる可能性があります。man などで確認をお願いします! **

(1)fork()すると標準出力も子プロセスに渡りますが、その子プロセスは終了時に渡された標準出力をフラッシュ&クローズするため、fork() 前にまだフラッシュしていない情報があると出力が重複してしまうことがあります。子プロセスを_exit()で終了するとこの処理が省かれるため、重複した出力が防げます。
なお、このパターンの場合はfork()の前にfflush(stdout)でフラッシュしておくことでも回避できるかと思います。

(2)fgets()を使う場合、fork()するとstdinの読み込み位置が初期化されてしまうことがあるようです。その結果、何度も繰り返し同じ行を読んでしまって出力が重複してしまうことがあります。英語ですが、Stack Overflowで近い現象のQAがありました。→Reading input using fgets returns duplicate lines in C

投稿2021/07/20 15:41

編集2021/07/20 15:42
segavvy

総合スコア958

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

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

SUNMOON_14

2021/07/22 01:04

_exit(1)で解決できました!ありがとうございます。
segavvy

2021/07/22 01:16

いえいえ、お役に立てたようで良かったです!
guest

0

うまく説明できませんが、
子プロセスがexitする際に、exitの内部でfflush(stdin)相当が行われますが、それによる影響のようです。
親プロセス側で、ftell(stdin)を実行するとエラーが返ります。

子プロセスでexitする前に、fclose(stdin);等を実行すれば良いかと思います。
fflush(stdin);fclose(stdin);exit(1);で、現象が出る。
fclose(stdin);exit(1);で、現象が出ない。

投稿2021/07/20 15:46

編集2021/07/20 15:51
otn

総合スコア84423

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

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

otn

2021/07/20 16:11

stdinはfcloseすればいいとして、stdoutはfcloseするとfflushもされてしまうので、困りましたね。 端末に出力しているときは行単位のバッファリングですが、 標準出力をファイルにリダイレクトしたりパイプで繋いだりした場合は、大きなサイズでのバッファリングなので出力が二重になってしまいます。 これを避けるには、標準出力をバッファリングしないようにsetvbuf()で設定するか、 exit()でなく、何もせずに終了する _exit() を使います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問