关于C#:编写自己的shell……卡在管道上?

Writing my own shell… stuck on pipes?

在过去的几天里,我一直在尝试编写自己的shell实现,但是我似乎一直无法使管道正常工作。我能够解析一条线并分叉各个管道之间的命令(例如:ls | sort),但似乎无法使它们将输入从一个管道输送到另一个管道。

我想我只是不了解如何正确使用dup2()和管道。

我现在已经包含了仍然失败的代码... :(所以卡住了...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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);
}

} ??


第一个建议:符号常量比魔术数字更好。

1
2
3
4
5
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_WRITE]传递给第一个子进程,该进程将dup2传递给1,然后将fd[PIPE_READ]传递给第二个子进程,将dup2设置为0。

简单地查看forkAndExecute'的原型表明它不能做到这一点:

1
2
void forkAndExecute( char* arrayOfWords[] , vector *vectorOfPIDs ,
    bool hasNextCmd , bool hasPrevCmd);

它仅处理一个命令,并且从该参数列表来看,除非诉诸邪恶的全局变量,否则它无法从其PrevCmd接收文件描述符或从其NextCmd接收文件描述符。 >

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


好,这对我有用。希望这对您有帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/************************
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);
}

常规过程将向该基本过程(伪代码)添加错误处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pipe(fds)
if (fork() is child) {
  dup2(fds[1], 1)
  close(fds[0])
  close(fds[1])
  exec("ls")
}
if (fork() is child) {
  dup2(fds[0], 0)
  close(fds[0])
  close(fds[1])
  exec("sort")
}
close(fds[0])
close(fds[1])
wait()

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


这是有关UNIX管道的教程,特别是关于如何在类似shell的体系结构中构造点线的说明:

http://www.cse.ohio-state.edu/~mamrak/CIS762/pipes_lab_notes.html

编写的代码虽然不多,但是它很好地描述了这些概念。

您还可以下载几乎所有shell的源代码,例如bash,tcsh,zsh等。


几年前,当我需要做类似的shell时,我使用了《实用Unix编程》一书。

对于许多IPC主题的示例确实有用。我的办公桌上还有我不时参考的副本。对于$ 2-$ 9的使用,这对于您得到的东西来说是一个相当不错的价值。

对于它的价值,只是以为我会提及它。


尝试阅读Bash的源代码以了解其工作方式。


这是我上学期系统编程课上关于管道的注释。


好吧,我没有答案,但是我正在研究同一问题的atm。我将分享我所拥有的。它适用于两个命令,但运行完成后,I / O中断。以一种奇怪的方式,我还无法弄清楚。叫水管工!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);
}

我感觉到它与wait()之后的正在做或不做的事情有关


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

代替流水线中n个进程的一般情况,您应该以2为底数并从那里开始扩展。如果继续扩展工作代码而不是直接针对复杂的结构进行射击,则将更好地理解文件描述符之间的插入方式。