web-dev-qa-db-ja.com

CでShellを実装していて、入出力リダイレクトの処理の助けが必要

ラウンド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);
}

私はそれを機能させるために別のことを試みてきたので、そこにいくつかの不要な材料があるかもしれません。何が悪いのかわかりません。

14
Jordan

リダイレクト後に開いているファイル記述子が多すぎます。 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);
}
_

私は慈善活動を行い、あなたが間違いを無視しているという事実を無視します。ただし、システムコールをエラーチェックする必要があります。

最初の段落では、ファイルを開き、変数fdにファイル記述子(3の場合が多い)を取り込みます。次に、ファイル記述子を標準入力(_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...
}
_

これを行う前に、おそらくfflush(0)を使用して、シェルの標準I/Oチャネルをすでにフラッシュしていることを確認する必要があります。これにより、フォークされた子が問題のために標準エラーに書き込む場合、無関係な重複出力はありません。

また、さまざまなopen()呼び出しのエラーチェックを行う必要があることにも注意してください。

15

リダイレクト後に開いているファイル記述子が多すぎます。必要なコードはこれです。

    if (pid == 0)
{          /* for the child process:         */

    // function for redirection ( '<' , '>' )

    int fd0,fd1,i,in=0,out=0;
    char input[64],output[64];

    // finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that

    for(i=0;argv[i]!='\0';i++)
    {
        if(strcmp(argv[i],"<")==0)
        {        
            argv[i]=NULL;
            strcpy(input,argv[i+1]);
            in=2;           
        }               

        if(strcmp(argv[i],">")==0)
        {      
            argv[i]=NULL;
            strcpy(output,argv[i+1]);
            out=2;
        }         
    }

    //if '<' char was found in string inputted by user
    if(in)
    {   

        // fdo is file-descriptor
        int fd0;
        if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
            perror("Couldn't open input file");
            exit(0);
        }           
        // dup2() copies content of fdo in input of preceeding file
        dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0 

        close(fd0); // necessary
    }

    //if '>' char was found in string inputted by user 
    if (out)
    {

        int fd1 ;
        if ((fd1 = creat(output , 0644)) < 0) {
            perror("Couldn't open the output file");
            exit(0);
        }           

        dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
        close(fd1);
    }

    execvp(*argv, argv);
    perror("execvp");
    _exit(1);

    // another syntax
    /*      if (!(execvp(*argv, argv) >= 0)) {     // execute the command  
            printf("*** ERROR: exec failed\n");
            exit(1);
     */ 
}


    else if((pid) < 0)
    {     
        printf("fork() failed!\n");
        exit(1);
    }

    else {                                  /* for the parent:      */

        while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors) 
    }
}
7

これが起こっていることです。 fork()を呼び出した後、元のプロセスの複製である2つのプロセスが実行されます。違いは、pidに格納されているfork()の戻り値です。

次に、両方のプロセス(シェルと子)がstdinとstdoutを同じファイルにリダイレクトします。以前のfdをcurrent_outに保存しようとしていたと思いますが、Seth Robertsonが指摘しているように、間違ったファイル記述子が保存されているため、これは現在機能しません。親もstdoutを復元しますが、stdinは復元しません。

このバグを修正することもできますが、もっとうまくできます。実際には親の出力をリダイレクトする必要はなく、子の出力のみをリダイレクトします。したがって、最初にpidを確認してください。その後、ファイル記述子を復元する必要もありません。

4
Greg Inozemtsev