1

我正在为大学作业制作一个迷你外壳。我们必须读入命令,从路径 var 中找到要执行的二进制文件,然后执行命令,无论有无管道。除了管道,我的一切都正常工作(我认为)。通过网络搜索,我已经能够构建一个测试程序,该程序使用两个硬编码命令并将一个管道连接到另一个,并获得预期的结果。现在,当我将该代码复制并粘贴到我的实际程序中时,第一个命令输出正常(实际上输出命令就像没有管道一样),而第二个我认为实际上没有做任何事情(第一个命令的输出不是通过管道传递到第二个)。

这是整个代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define BUFFSIZE 1024
#define MAXWORDS 17
#define MAXCHAR  64

static char *path;
extern char **environ;

//split cmd "string" on pipe (|) symbol
void split(char **pipe, char **left, char **right, int n)
{
    int i, x;

    for(i = 0; i < n; i++)
    {
        if (strchr(&pipe[i][0], '|') != 0)
        {
            for(x = 0; x < i; x++)
                strcpy(left[x], pipe[x]);
            left[x++] = 0;

            break;
        }
    }

    i++;
    for(x = 0; i < n; x++)
        strcpy(right[x], pipe[i++]);
    right[x++] = 0;
}

//Find directory where cmd can be executed from (PATH or direct access)
char *finddir(char *s)
{
    char *pp;
    char *pf;
    int   ok;
    strcpy(path, getenv("PATH"));
    pp = strtok(path, ":");
    while (pp != NULL)
    {
        pf = (char *)malloc(strlen(pp) + strlen(s) + 2);
        if (pf == NULL)
        {
            fprintf(stderr, "Out of memory in finddir\n");
            return NULL;
        }

        strcpy(pf,pp);
        strcat(pf,"/");
        strcat(pf,s);
        ok = !access(pf, X_OK);
        free(pf);
        if (ok)
            return pp;

        pp = strtok(NULL, ":");
    }
    return NULL;
}

int cmdcheck(char *cmd, char *p)
{
    char *dir;

    if (strchr(p, '/') != NULL)
        sprintf(cmd, "%s\0", p);
    else
    {
        dir = finddir(p);
        if (dir == NULL)
            return 1;
        else 
            sprintf(cmd, "%s/%s\0", dir, p);
    }

    return 0;
} 

void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
    int pid;
    int status;

    switch (pid = fork())
    {
        case 0:     //Child
            dup(pfd[0]);
            close(pfd[1]);          //the child does not need this end of the pipe 
            execve(cmd2, p2, environ);
            perror(cmd2);

        default:    //Parent
            dup(pfd[1]);
            close(pfd[0]);          //the parent does not need this end of the pipe 
            execve(cmd1, p1, environ);
            perror(cmd1);

        case -1:    //ERROR
            perror("fork-RP");
            exit(1);
    }
}   

int main(void)
    {
        int status;         //read status when reading cmd in
        char ch;            //character currently reading
        int n, i, x;            //(n) count of chars read; (i) cmd args iter; (x) cmd arg iter in cmd array
        char buffer[BUFFSIZE];      //read buffer
        char *token;            //token var when splitting buffer
        int pid0, pid1, pid2;       //return ID from fork call
        int which;          //return value from wait (child pID that just ended)
        char msg[100];          //messages to print out
        char *cmd1, *cmd2;      //cmds when piping
        char *params[MAXWORDS];     //cmd parameters to send to execve
        int fd[2];          //pipe file descriptors
        char *pparam1[MAXWORDS];    //cmd "string" on left side of pipe
        char *pparam2[MAXWORDS];    //cmd on right side of pipe

        for(;;)
        {
            for (i = 0; i < MAXWORDS; i++)
                params[i] = malloc(MAXCHAR);

            n = 0;
            write(1, "# ", 2);

            for(;;)
            {
                status = read(0, &ch, 1);
                if (status == 0)
                    return 0;   //End of file
                if (status == -1)
                    return 1;   //Error

                if(n == BUFFSIZE)
                {
                    write(1, "Line too long\n", 14);
                    return 1;
                }

                buffer[n++] = ch;

                if(ch == '\n')
                    break;
            }

            buffer[n] = '\0';

            x = 0;
            token = strtok(buffer, " \t\n\0");
            while(token != NULL)
            {
                strcpy(params[x++], token);
                token = strtok(NULL, " \t\n\0");
            }
            params[x] = 0;

            path = getenv("PATH");
            if (path == NULL)
            {
                fprintf(stderr, "PATH environment variable not found.\n");
                return 1;
            }

            n = strlen(path);
            path = (char *)malloc(n+1);
            if (path == NULL)
            {
                fprintf(stderr, "Unable to allocate space for copy of PATH.\n");
                return 1;
            }

            cmd1    = malloc(MAXCHAR);
            cmd2    = malloc(MAXCHAR);
            for (i = 0; i < MAXWORDS; i++)
                pparam1[i] = malloc(MAXCHAR);
            for (i = 0; i < MAXWORDS; i++)
                pparam2[i] = malloc(MAXCHAR);

            split(params, pparam1, pparam2, x);

            //Check first cmd
            if(cmdcheck(cmd1, pparam1[0]))
            {
                sprintf(msg, "cmd '%s' is not executable\n", pparam1[0]);
                write(1, msg, strlen(msg));
                break;
            }

            //Check second cmd
            if(cmdcheck(cmd2, pparam2[0]))
            {
                sprintf(msg, "cmd '%s' is not executable\n", pparam2[0]);
                write(1, msg, strlen(msg));
                break;
            }

            pipe(fd);

            switch (pid0 = fork())
            {
                case 0:     //Child
                    switch (pid1 = fork())
                    {
                        case 0:     //Child
                            runpipe(fd, cmd1, pparam1, cmd2, pparam2);
                            exit(0);

                        default:
                            exit(0);
                            //break;

                        case -1:    //ERROR
                            perror("fork-2");
                            exit(1);
                    }

                default:    //Parent
                    which = wait(&status);
                    if (which == -1)
                    {
                        write(1, "wait failed\n", 12);
                        exit(1);
                    }

                    if (status & 0xff)
                        sprintf(msg, "process %d terminated abnormally for reason %d\n", which, status & 0xff);
                    else
                        sprintf(msg, "process %d terminated normally with status %d\n", which, (status >> 8) & 0xff);

                    write(1, msg, strlen(msg));
                    break;

                case -1:    //ERROR
                    perror("fork-1");
                    exit(1);
            }

            free(cmd1);
            free(cmd2);
            for (i = 0; i < MAXWORDS; i++)
                free(pparam1[i]);
            for (i = 0; i < MAXWORDS; i++)
                free(pparam2[i]);

            free(path);
            for (i = 0; i < MAXWORDS; i++)
                free(params[i]);
        }

        return 0;   
    }

打字回声一| 提示符处的wc -l将仅输出一个带有相应等待打印语句的语句。自从我使用 C 以来已经有几年了,所以我走在正确的轨道上吗?

谢谢。

编辑:这是现在的运行管道功能。但唯一打印的是等待语句。

void runpipe(int pfd[], char *cmd1, char *p1[], char *cmd2, char *p2[])
{
    const int READ = 0;
    const int WRITE = 1;
    int pid;
    int status;

    switch (pid = fork())
    {
        case 0:     //Child
            close(pfd[WRITE]);
            dup2(pfd[READ], STDIN_FILENO);
            close(pfd[READ]);
            execve(cmd2, p2, environ);
            perror(cmd2);

        default:    //Parent
            close(pfd[READ]);
            dup2(pfd[WRITE], STDOUT_FILENO);
            close(pfd[WRITE]);
            execve(cmd1, p1, environ);
            perror(cmd1);

        case -1:    //ERROR
            perror("fork-RP");
            exit(1);
    }
}
4

2 回答 2

1

那里发生了一些导致意外行为的事情。

首先是你分叉太多了。如果你将你的runpipe()函数调用展开到 switch 语句中main(),你会看到你达到了曾孙级别:

switch (pid0 = fork())
{
    case 0:     // Child
        switch (pid1 = fork())
        {
            case 0:     // GRAND-Child
                   // function call to runpipe()
                   switch (pid = fork())
                    {
                        case 0:     // GREAT-GRAND-Child
                            close(pfd[WRITE]);
                            dup2(pfd[READ], STDIN_FILENO);
                            close(pfd[READ]);
                            execve(cmd2, p2, environ);
                            perror(cmd2);

                        default:    // GRAND-Child
                            close(pfd[READ]);
                            dup2(pfd[WRITE], STDOUT_FILENO);
                            close(pfd[WRITE]);
                            execve(cmd1, p1, environ);
                            perror(cmd1);

这是没有必要的。分叉一次,main()然后调用你的runpipe()函数。

与此问题相关的是您创建管道的位置。当您分叉时,新创建的子进程会继承父进程的所有打开文件(以及许多其他内容)。这包括默认描述符 0、1 和 2(stdin、stdout 和 stderr),以及任何其他打开的文件,包括您创建的名为fd. 这意味着父母、孩子、孙子和曾孙都继承了管道两端的副本。您正确地关闭了runpipe()函数内未使用的末端(孙子和曾孙的副本),但您的main()函数中的父级和子级也有副本!

由于使用管道的唯一一对进程是在 中创建的runpipe(),因此您可以将声明fd和调用移动pipe(2)到该函数中。

这两个修改将解决您的问题。

一个与你的 shell 流程完全无关的问题是你main()最终wait(2)在函数的“父”进程上执行它runpipe()。由于该父级是 running cmd1,因此您的 shell 将在完成后立即返回其提示cmd1,而不是在管道中的最后一个命令(cmd2在本例中)完成时返回。echo | sleep 10您可以通过在 shell 和真实 shell 中运行类似的东西来查看行为差异。

于 2013-03-04T18:39:32.050 回答
0

dup函数复制一个文件描述符,并返回新的副本。但是,这将不起作用,因为stdin子项仍然存在,并且新的文件描述符不会代替标准输入。

您必须先关闭标准输入文件描述符,然后再执行dup. 或者dup2在复制之前使用 which 将首先自动关闭目标文件描述符:

dup2(pfd[0], STDIN_FILENO);
于 2013-03-03T21:10:54.020 回答