20

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

私はそれを機能させるためにさまざまなことを試みてきたので、そこに不要な資料があるかもしれません. 何が問題なのかわかりません。

4

3 に答える 3

21

リダイレクト後に開いているファイル記述子が多すぎます。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 番目の段落でプロセスを多かれ少なかれ繰り返します。最初に、fd = creat(...)呼び出しで開いているファイル記述子 3 の唯一のレコードを上書きしますが、新しい記述子 (おそらく 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()呼び出しをエラーチェックする必要があることに注意してください。

于 2012-07-17T08:00:55.333 に答える
7

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

    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) 
    }
}
于 2015-09-20T20:59:43.883 に答える
4

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

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

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

于 2012-07-17T03:11:55.793 に答える