一尘不染

在C中实现Shell并需要帮助来处理输入/输出重定向

linux

第二回合

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

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

我可能那里有一些不必要的材料,因为我一直在尝试不同的方法来使其正常工作。我不确定出了什么问题。


阅读 240

收藏
2020-06-03

共1个答案

一尘不染

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

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()覆盖输出。但是,最好不要更改父Shell的文件描述符。它比重新复制文件描述符要少。

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

因此,您已经将主外壳程序的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 %s\n", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

在执行任何此操作之前,您应该确保已使用刷新了Shell的标准I /
O通道fflush(0),这样,如果派生的子级由于问题而写入标准错误,则不会有多余的重复输出。

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

2020-06-03