C++ 写我自己的外壳。。。卡在管道上?

C++ 写我自己的外壳。。。卡在管道上?,c++,unix,shell,pipe,C++,Unix,Shell,Pipe,在过去的几天里,我一直在尝试编写自己的shell实现,但似乎在让管道正常工作方面遇到了困难。我能够分别解析一行和分叉管道之间的命令(例如:ls | sort),但似乎无法将它们从一个管道输入到另一个管道 我想我只是不明白如何正确使用dup2()和管道 我现在已经包含了仍然失败的代码…:(太困了 void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool

在过去的几天里,我一直在尝试编写自己的shell实现,但似乎在让管道正常工作方面遇到了困难。我能够分别解析一行和分叉管道之间的命令(例如:ls | sort),但似乎无法将它们从一个管道输入到另一个管道

我想我只是不明白如何正确使用dup2()和管道

我现在已经包含了仍然失败的代码…:(太困了

void forkAndExecute( char* arrayOfWords[] , vector<pid_t> *vectorOfPIDs , bool hasNextCmd , bool hasPrevCmd) {

int fd[ 2 ];
pid_t pid;

if( hasNextCmd ){
    pipe(fd);
}

pid = fork();

//error if PID < 0
if( pid < 0 ) {
    cerr << ">>> fork failed >>>" << endl;
    exit(-1);
}
//child process if PID == 0
else if( pid == 0 ) {
    if ( hasPrevCmd ){
        dup2(fd[0] , 0);
        close(fd[0]);
        close(fd[1]);

    }
    if ( hasNextCmd ){
        dup2(fd[1],1);
        close(fd[0]);
        close(fd[1]);
    }
    execvp( arrayOfWords[0] , arrayOfWords );
    cout << ">>> command not found >>>" << endl;
    //if logic reaches here, exec failed
    exit(0);
} 
//parent process
else{
    close(fd[0]);
    close(fd[1]);
    //if( ! isLastCmd ){

    //}
    vectorOfPIDs->push_back(pid);
}
void-forkAndExecute(char*arrayOfWords[],vector*vectorOfPIDs,bool-hasNextCmd,bool-hasPrevCmd){
int-fd[2];
pid_t pid;
if(hasNextCmd){
管道(fd);
}
pid=fork();
//PID<0时出错
if(pid<0){

cerr这里有一个关于UNIX管道的教程,特别是关于如何在类似shell的体系结构中构造管道:

虽然没有多少完整的代码,但它很好地描述了这些概念


您还可以下载几乎任何shell的源代码,如、、等。

尝试阅读,看看它们是如何完成的。

是我上学期上的系统编程课上关于管道的笔记。

几年前,当我需要编写类似的shell时,我使用了这本书

对于许多IPC主题的例子来说,它非常有用。我桌上还有一份我不时参考的副本。使用2美元到9美元,它的价值相当高


不管它值多少钱,我想我会提到它。

一般流程会将错误处理添加到此基本流程(伪代码):


首先创建管道。然后分叉子进程,以便它们继承它。将文件描述符重新映射为0(stdin)和1(stdout)因此,进程读取和写入适当的位置。关闭所有不希望子进程看到的剩余文件描述符,或在工作完成时阻塞。执行实际的子进程。等待它们完成,您就完成了!

第一个建议:符号常量比幻数更好

const int PIPE_READ = 0;
const int PIPE_WRITE = 1;
int fd[2];
pipe(fd);
// Now you can refer to fd[PIPE_READ] and fd[PIPE_WRITE].
第二个建议:后退一步,想想你想要完成什么

您希望生成两个进程,第一个进程的stdout连接到第二个进程的stdin,对吗

因此,在C中,这意味着您需要调用
pipe
,将
fd[pipe\u WRITE]
传递给第一个子进程,这将
dup2
它传递给1,并将
fd[pipe\u READ]
传递给第二个子进程,这将
dup2
它传递给0

只需查看
forkAndExecute'
s原型,它就无法做到这一点:

void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs , 
    bool hasNextCmd , bool hasPrevCmd);
它只处理一个命令,从参数列表来看,除非它使用邪恶的全局变量,否则它无法从其PrevCmd接收文件描述符或从其NextCmd接收文件描述符


考虑如何管理所需的文件描述符,并重新设计
forkAndExecute
,以便能够使用这些描述符。

您正在将每个程序的输入连接到自己的输出。您可能希望将每个程序的输出连接到下一个程序的输入


与管道中n个进程的一般情况不同,您应该从a开始并从那里展开。如果您继续扩展工作代码,而不是直接针对复杂的结构,您将更好地理解文件描述符彼此插入的方式。

我没有答案,b但我正在处理同一个atm问题。我将分享我所拥有的。它适用于两个命令,但在它运行完后,I/o被破坏。奇怪的是,我还没有弄清楚。给水管工打电话

void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  int oldIn, oldOut;
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    oldIn = dup(STDIN_FILENO);
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    execvp(cmd2[0], cmd2);
  // child process #2
  } else if ((fork()) == 0) {
    oldOut = dup(STDOUT_FILENO);
    // Reassign stdout to fds[1] end of pipe.
    dup2(fds[1], STDOUT_FILENO);
    close(fds[0]);
    close(fds[1]);
    // Execute the first command.
    execvp(cmd1[0], cmd1);
  // parent process
  } else
    wait(NULL);
    dup2(oldIn, STDIN_FILENO);
    dup2(oldOut, STDOUT_FILENO);
    close(oldOut);
    close(oldIn);
}

我有一种感觉,这与我在等待之后做了什么或没有做什么有关()

好的,这对我很有用。希望这能帮助你:

/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    // child process #2
    if (fork() == 0) {
        // Reassign stdout to fds[1] end of pipe.
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        // Execute the first command.
        execvp(cmd1[0], cmd1);
    }
    wait(NULL);
    execvp(cmd2[0], cmd2);
    }
    close(fds[1]);
    close(fds[0]);
    wait(NULL);
}

事实上,它不像我想象的那么假。行尾有分号,拼写“is child”由于
==0
并使用了相应的
exec
wait
变量,它编译为有效的C并在第二次尝试时完成了工作。需要两个
wait
调用。首先,感谢您的帮助。其次,我仍然被卡住了呵呵。我真的不确定它为什么不工作。“卡住了”怎么做?你试过我发布的示例代码了吗?它真的很有效(转换成C,见我的第一条评论),我在发布后检查了它。你没有将这个概念应用到你的代码库中吗?问题是我有N个管道。所以每次我使用fork和execute方法时,我只会fork一次。我在理解如何保存和使用旧文件描述符方面遇到困难。JB你能详细说明一下“需要两个等待调用”吗拜托?我一直在给自己做心理准备。我要把你的建议放在心上…退一步,一步一步地做这件事,不要让我感到太累。我认为,一旦一个管道的方法完成,你就可以用递归来处理多个管道。我不清楚你会如何递归地实现它,但为什么不呢。是什么让它成为somEW棘手的是,在管道本身被创建之前,我们不能生成
pr | pr
模式的任何进程。在多管道设置中,这扩展到:在输入和输出管道都被创建之前,我们不能生成任何进程。您的oldIn/oldOut变量都是无用的,并且可能是您的问题的原因。您可以在父管道中关闭它们进程,但仅在子进程中为它们赋值。编译器可能会将它们默认为0,实际上是永久关闭stdin(fd 0)。此外,在您等待之前,您需要关闭父进程中的管道;否则,读取器进程可能会阻塞,等待来自仍然打开的另一端的数据。IMHO,阅读管道、dup,可能还有fork、exec和wait的手册页就不那么困难了。这样,他就可以通过理解一般机制来学习研究一个具体而复杂的例子。
/************************
function: void pipeCommand(char** cmd1, char** cmd2)
comment: This pipes the output of cmd1 into cmd2.
**************************/
void pipeCommand(char** cmd1, char** cmd2) {
  int fds[2]; // file descriptors
  pipe(fds);
  // child process #1
  if (fork() == 0) {
    // Reassign stdin to fds[0] end of pipe.
    dup2(fds[0], STDIN_FILENO);
    close(fds[1]);
    close(fds[0]);
    // Execute the second command.
    // child process #2
    if (fork() == 0) {
        // Reassign stdout to fds[1] end of pipe.
        dup2(fds[1], STDOUT_FILENO);
        close(fds[0]);
        close(fds[1]);
        // Execute the first command.
        execvp(cmd1[0], cmd1);
    }
    wait(NULL);
    execvp(cmd2[0], cmd2);
    }
    close(fds[1]);
    close(fds[0]);
    wait(NULL);
}