1. ホーム
  2. c

[解決済み] C言語でシェルを実装し、入出力のリダイレクションを処理する手助けが必要です。

2022-02-19 10:28:49

質問内容

第2ラウンド

いくつかの回答を読んで、私の修正したコードは。

int pid = fork();

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {   

    if (in) { //if '<' char was found in string inputted by user
        int fd0 = open(input, O_RDONLY, 0);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
        in = 0;
    }

    if (out) { //if '>' was found in string inputted by user
        int fd1 = creat(output, 0644);
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
        out = 0;
    }   

    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    free(res);
}

動作はするのですが、標準出力が再接続されていないとか、そのようなことがあるようです。以下は実行です。

SHELL$ cat > file
hello, world
this is a test
SHELL$ cat < file //no output
SHELL$ ls //no output

'<' と '>' はどちらも動作しますが、実行後は何も出力されません。


第1ラウンド

C言語で比較的簡単なシェルを作っているのですが、入力(<)と出力(>)のリダイレクトの実装に困っています。以下のコードの問題点を見つけるのを手伝ってください。

int fd;
int pid = fork();
int current_out;

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_out = dup(0);
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {       
    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    dup2(current_out, 1);
    free(res);
}

いろいろと試行錯誤しているので、不要なものが入っているかもしれません。何が間違っているのかよくわかりません。

解決方法は?

リダイレクトの後、ファイルディスクリプタを開きすぎています。 2つの段落を分解してみましょう。

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_in = dup(0);  // Fix for symmetry with second paragraph
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

エラーを無視しているのはご愛敬です。 ただし、システムコールのエラーチェックは必要です。

最初の段落では、ファイルを開き、ファイル記述子(3かもしれない)を変数 fd . 次に、そのファイルディスクリプタを標準入力に複製します ( STDIN_FILENO ). ただし、ファイルディスクリプタ3はまだ開いていることに注意。 次に dup(0) (一貫性を保つため、本来は STDIN_FILENO を取得し、別のファイル記述子(おそらく4)を取得します。 つまり、ファイル記述子 0、3、4 が同じファイルを指していることになります(実際、同じオープン ファイル記述子があります - オープン ファイル記述子はオープン ファイル記述子とは異なることに注意してください)。 もし、あなたが current_in (親)シェルの標準入力を保持するため、そのようにしなければなりません。 dup() を実行する前に dup2() で出力を上書きします。 しかし、親シェルのファイルディスクリプタを変更しない方が良いでしょう。ファイルディスクリプタを再複製するよりもオーバーヘッドが少なくて済みます。

次に、2番目の段落の処理をほぼ繰り返し、まずファイル記述子3が開かれている唯一の記録を fd = creat(...) を呼び出しますが、新しい記述子、おそらく5を取得し、それを標準出力に複製します。 次に dup(1) その結果、別のファイル記述子、おそらく6が生成されます。

つまり、メインシェルのstdinとstdoutがファイルにリダイレクトされている(そして、それを元の値に戻す方法がない)状態になっているわけです。 したがって、最初の問題は、リダイレクトを行う前に fork() の後に行う必要があります。 fork() - ただし、プロセス間のパイプを作成する場合は、フォークする前にパイプを作成する必要があります。

2つ目の問題は、大量のファイルディスクリプタをクローズする必要があり、そのうちの1つはもはや参照先がないことです。

そこで、必要になるかもしれないのが

if ((pid = fork()) < 0)
    ...error...
else if (pid == 0)
{
    /* Be childish */
    if (in)
    {
        int fd0 = open(input, O_RDONLY);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
    }

    if (out)
    {
        int fd1 = creat(output , 0644) ;
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
    }
    ...now the child has stdin coming from the input file, 
    ...stdout going to the output file, and no extra files open.
    ...it is safe to execute the command to be executed.
    execve(cmd[0], cmd, env);   // Or your preferred alternative
    fprintf(stderr, "Failed to exec %s\n", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

この作業を行う前に、シェルの標準 I/O チャンネルをすでにフラッシュしていることを確認する必要があります。 fflush(0) そうすれば、もしフォークされた子プロセスが問題のために標準エラーに書き込んでも、余計な重複出力が発生しません。

また、様々な open() の呼び出しは、エラーチェックが必要です。