C编程管只有一半工作

时间:2013-03-03 21:05:41

标签: c linux fork pipe

我正在为一个大学作业开发迷你外壳。我们必须读入命令,从路径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;   
    }

键入 echo one | wc -l <​​/ em>在提示符下只会输出一个以及相应的wait print语句。我使用C已经有几年了,所以我走在正确的轨道上了吗?

感谢。

编辑: 这是现在的runpipe功能。但唯一印刷的是等待声明。

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

2 个答案:

答案 0 :(得分:1)

有一些事情正在导致意外行为。

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

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()功能。

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

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

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

与shell流程相关的一个完全不相关的问题是,main()最终会在wait(2)函数的“父”进程中执行runpipe()。由于该父级是运行cmd1的父级,因此一旦cmd1完成,您的shell将返回其提示,而不是在管道中的最后一个命令(在这种情况下为cmd2)饰面。您可以通过在shell和真正的shell中运行类似echo | sleep 10的内容来查看行为差异。

答案 1 :(得分:0)

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

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

dup2(pfd[0], STDIN_FILENO);