C Exeve和管道问题-如何恢复原始管道?

C Exeve和管道问题-如何恢复原始管道?,c,linux,unix,pipe,dup,C,Linux,Unix,Pipe,Dup,我一直在做一个简单的外壳,它可以做管道 下面是一些用于操作管道语法的代码 int fd[2]; int stdin_copy; int stdout_copy; int status; char * msg; if (pipe(fd) == -1) { perror("pipe"); exit(1); } // fd[0] : process read from fd[0] // fd[1] : process write to fd[1] if (execok(pr_wo

我一直在做一个简单的外壳,它可以做管道

下面是一些用于操作管道语法的代码

int fd[2];
int stdin_copy;
int stdout_copy;

int status;
char * msg;

if (pipe(fd) == -1) {
    perror("pipe");
    exit(1);
}
// fd[0] : process read from fd[0]
// fd[1] : process write to fd[1]

if (execok(pr_words) == 0) { /* is it executable? */
    status = fork(); /* yes; create a new process */

    if (status == -1) { /* verify fork succeeded */
        perror("fork");
        exit(1);
    } else if (status == 0) { /* in the child process... */
        stdout_copy = dup(1);

        close(1); // close standard output        
        dup(fd[1]);
        close(fd[0]);
        close(fd[1]); // close and fd[1] will be stdout 
        pr_words[l_nwds] = NULL; /* mark end of argument array */

        status = execve(path, pr_words, environ); /* try to execute it */

        perror("execve"); /* we only get here if */
        exit(0); /* execve failed... */
    }
    /*------------------------------------------------*/
    /* The parent process (the shell) continues here. */
    /*------------------------------------------------*/
    else if (status > 0) { // in the parent process....
        wait( & status); /* wait for process to end */

        if (execok(af_words) == 0) {
            if (pipe(fd2) == -1) {
                perror("pipe");
                exit(1);
            }

            status = fork();

            if (status == -1) {
                perror("fork");
                exit(1);
            } else if (status == 0) { // in the child process...
                stdin_copy = dup(0);
                close(0);
                dup(fd[0]);

                close(fd[1]);
                close(fd[0]);

                read(fd[0], readbuffer, sizeof(readbuffer));

                af_words[r_nwds] = NULL; /* mark end of argument array */
                status = execve(path, af_words, environ); /* try to execute it */

            } else if (status > 0) {
                wait( & status);

                msg = "over";
                write(2, msg, strlen(msg));
                close(fd[0]);
                close(fd[1]);
                dup2(stdin_copy, 0);
                dup2(stdout_copy, 1);
                close(stdin_copy);
                close(stdout_copy);
                printf("%s", "hi");
            }
        } else {
            /*----------------------------------------------------------*/
            /* Command cannot be executed. Display appropriate message. */
            /*----------------------------------------------------------*/
            msg = "*** ERROR: '";
            write(2, msg, strlen(msg));
            write(2, af_words[0], strlen(af_words[0]));
            msg = "' cannot be executed.\n";
            write(2, msg, strlen(msg));
        }

    }

} else {
    /*----------------------------------------------------------*/
    /* Command cannot be executed. Display appropriate message. */
    /*----------------------------------------------------------*/
    msg = "*** ERROR: '";
    write(2, msg, strlen(msg));
    write(2, pr_words[0], strlen(pr_words[0]));
    msg = "' cannot be executed.\n";
    write(2, msg, strlen(msg));
}
pr_字和af_字是包含命令、管道右侧和左侧的二维指针。(例如ls | cat->pr_words=“ls\0”,af_words=“cat\0”)

首先,我使用fork()创建子进程,并为标准输出注册fd[1]。(并在关闭stdin之前保存stdin文件描述符)并在执行命令左侧后,进行其他子进程来处理命令右侧

类似地,我在关闭标准输出之前保存了标准输出文件描述符,并进行了fd[0]标准输入。通过使用execve函数的第一个结果的输入,我认为每个结果都会保存在fd[1]中。(因为它当前已注册为std输出)

最后,将管道输入和输出恢复为标准输出。(我不想使用dup2,但由于缺乏知识,我别无选择)

但是,在执行此代码时,在输入'ls | cat'后,没有输出。此外,我设置终端的每个条目都将打印“#”。(这意味着“#ls”或“#cat”…)但在输入上述管道命令后,该程序甚至不打印“#”

我猜这个程序的输入和输出流在处理管道命令后完全扭曲了


我怎样才能修好它?我的意思是,我希望将第一次执行的结果保存到fd[1]中,并在使用此fd[1]执行第二次执行后,通过标准输出文件描述打印最终结果

我发现您的代码至少存在一些问题:

首先,您不应该在启动第二个进程之前等待第一个进程()。管道中只有几KB的缓冲区,在此之后,如果第一个子进程试图继续在那里写入,shell将挂起。您需要先启动两个孩子,然后再等待他们中的每一个。只需将第一个等待(&status)呼叫向下移动到另一个呼叫旁边即可。您可能希望稍后使用waitpid或其他方法,以便知道哪个先完成,哪个状态进入哪个状态,但一旦基本工作开始,您就可以解决这个问题

其次,当您使用fork()时,会复制程序中的所有变量和文件描述符映射。因此,您不需要在任一子进程中保存stdin或stdout,因为您在子进程中所做的任何更改都不会影响父进程。此外,由于只在子进程中初始化stdin_copy和stdout_copy,因此在第二个fork()之后在父进程中使用的变量的版本将被取消初始化。这就是导致父shell的I/O在执行此代码后出错的原因。在第二次分叉之后,实际上不需要在父级中执行任何操作来维护原始的stdin和stdout——在此之前,您永远不会在该过程中更改它们。您可能希望从post fork父代码中删除所有这些内容:

            close(fd[0]);
            close(fd[1]);
            dup2(stdin_copy, 0);
            dup2(stdout_copy, 1);
            close(stdin_copy);
            close(stdout_copy);
第三,为什么在第二个孩子中调用execve()之前要阅读管道?这只会将数据从管道中剥离出来,而您的执行孩子将永远看不到这些数据。这可能是导致管道本身无法工作的原因。您可能希望删除以下内容:

read(fd[0], readbuffer, sizeof(readbuffer));    
最后,这一行可能需要放在execok()调用之前(对于另一个类似的调用也是如此):

代码的框架应该如下所示,不进行错误处理和execok检查,如果您想知道哪个状态代码是哪个子级的,则演示waitpid()的用法:

int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
    // first child, close stdout and replace with pipe, then exec
} else {
    child_pid[1] = fork();
    if (child_pid[1] == 0) {
        // second child, close stdin and replace with pipe, then exec
    } else {
        // parent, and now we have the pids of the children
        waitpid(child_pid[0], &status, 0); // wait for first child
        waitpid(child_pid[1], &status, 0); // wait for second child
        // *do not* mess with stdin/stdout, they are okay here
    }
}

我真的采纳了你的建议。然而,在阅读了你的答案之后,我还有一个问题。除了第一个,其他一切都是可以理解的。要连续编写fork()函数吗?status1=fork();status2=fork();像这样吗?不,不要修改调用fork()的方式,这一部分很好。您需要将fork()之间的wait(&status)调用移动到这两个fork()之后,因此在流程结束时,您将连续两次调用wait(&status)。
int child_pid[2];
child_pid[0] = fork();
if (child_pid[0] == 0) {
    // first child, close stdout and replace with pipe, then exec
} else {
    child_pid[1] = fork();
    if (child_pid[1] == 0) {
        // second child, close stdin and replace with pipe, then exec
    } else {
        // parent, and now we have the pids of the children
        waitpid(child_pid[0], &status, 0); // wait for first child
        waitpid(child_pid[1], &status, 0); // wait for second child
        // *do not* mess with stdin/stdout, they are okay here
    }
}