用C语言实现shell,需要帮助处理输入/输出重定向



第二轮

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

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);
}

我可能有一些不必要的材料,因为我一直在尝试不同的方法来让它发挥作用。我不确定出了什么问题。

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

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

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

因此,您可以将主shell的stdin和stdout重定向到文件(并且无法将它们恢复为原始值)。因此,您的第一个问题是在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 %sn", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

在执行这些操作之前,您应该确保您已经刷新了shell的标准I/O通道,可能是通过使用fflush(0),这样,如果分叉的子级由于问题而写入标准错误,就不会有多余的重复输出。

还要注意,应该对各种open()调用进行错误检查。

重定向后打开的文件描述符太多。你需要的代码是这样的。

    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]!='';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 failedn");
            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) 
    }
}

下面是发生的事情。在调用fork()之后,有两个进程正在执行,它们是原始进程的副本。差异在于存储在pid中的fork()的返回值。

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

你可以修复这个错误,但你可以做得更好。实际上,您不必重定向父对象的输出,只需重定向子对象的输出。因此,只需先检查pid即可。然后也不需要恢复任何文件描述符。

最新更新