20

第二轮

在阅读了一些答案后,我修改后的代码是:

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 编写一个相对简单的 shell,但在实现输入 (<) 和输出 (>) 重定向时遇到了麻烦。帮我找出以下代码中的问题:

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

重定向后打开的文件描述符太多。让我们剖析这两段:

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是保留(父)shell 的标准输入,则必须dup()在执行dup2()覆盖输出。但是,最好不要更改父 shell 的文件描述符;它比重新复制文件描述符的开销更少。

然后您或多或少地重复第二段中的过程,首先覆盖文件描述符 3 的唯一记录,该记录通过fd = creat(...)调用打开但获得一个新的描述符,可能是 5,然后将其复制到标准输出中。然后你做一个dup(1),产生另一个文件描述符,也许是 6。

因此,您将主 shell 的标准输入和标准输出重定向到文件(并且无法将它们恢复为原始值)。因此,您的第一个问题是您正在执行重定向fork();你应该在之后做fork()——尽管当你在进程之间进行管道时,你需要在分叉之前创建管道。

您的第二个问题是您需要关闭过多的文件描述符,其中一个您不再有参考。

因此,您可能需要:

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...
}

在您执行任何此操作之前,您应该确保您已经刷新了 shell 的标准 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(),有两个进程正在执行,它们是原始进程的副本。不同之处在于其返回值fork()存储在pid.

然后两个进程(shell 和子进程)将它们的标准输入和标准输出重定向到相同的文件。我认为您试图将以前的 fd 保存在 中current_out,但正如 Seth Robertson 指出的那样,这目前不起作用,因为正在保存错误的文件描述符。父级还恢复其标准输出,但不恢复标准输入。

你可以修复这个错误,但你可以做得更好。您实际上不必重定向父母的输出,只需重定向孩子的输出。所以简单地先检查一下pid。然后也不需要恢复任何文件描述符。

于 2012-07-17T03:11:55.793 回答