【问题标题】:Implementing piping to mimic Shell实现管道以模仿 Shell
【发布时间】:2016-03-18 15:39:42
【问题描述】:

读者。我正在编写一个模仿 linux shell 的 C 程序。在实现管道之前,我的代码在输出/输入文件上正常工作。接下来是实现命令管道,例如 (a|b)|c。以下是我的代码:

测试它,我得到了“sfhjdj”、“exit”和“cd”的正确返回。我遇到的问题是运行一个简单的命令会返回ls: write error: Bad file descriptor。尝试管道也只运行第一个函数。知道是什么原因造成的吗?正如我从这里的其他问题中看到的那样,我尝试进行管道传输。

以下是我在实现管道之前的代码,我找不到错误,但可能是因为关闭/重复。感谢您阅读本文!

只有在执行函数中发生了变化。

编辑:帮助代码,prase.c

/*
 * parse.c - feeble command parsing for the Feeble SHell.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "parse.h"
#include "error.h"


#define MAXARGV 1000

enum token {
    identifier, directin, directout, doubledirectout,
    /* everything >= semicolon ends an individual "struct pipeline" */
    semicolon,
    ampersand,
    verticalbar, doubleampersand, doublebar, doublepipe,
    eol
};
static enum token gettoken(char **s, char **argp);
static char *ptok(enum token tok);


struct parsed_line *parse(char *s)
{
    struct parsed_line *retval;  /* remains freeparse()able at all times */
    struct parsed_line *curline;
    struct pipeline **plp;  /* where to append for '|' and '|||' */
    char *argv[MAXARGV];
    enum token tok;
    int argc = 0;
    int isdouble = 0;

    retval = curline = emalloc(sizeof(struct parsed_line));
    curline->conntype = CONN_SEQ;  /* i.e. always do this first command */
    curline->inputfile = curline->outputfile = NULL;
    curline->output_is_double = 0;
    curline->isbg = 0;
    curline->pl = NULL;
    curline->next = NULL;
    plp = &(curline->pl);

    do {
        if (argc >= MAXARGV)
            fatal("argv limit exceeded");
        while ((tok = gettoken(&s, &argv[argc])) < semicolon) {
            switch ((int)tok) {  /* cast prevents stupid warning message about
                                  * not handling all enum token values */
            case identifier:
                argc++;  /* it's already in argv[argc];
                          * increment to represent a save */
                break;
            case directin:
                if (curline->inputfile) {
                    fprintf(stderr,
                            "syntax error: multiple input redirections\n");
                    freeparse(curline);
                    return(NULL);
                }
                if (gettoken(&s, &curline->inputfile) != identifier) {
                    fprintf(stderr, "syntax error in input redirection\n");
                    freeparse(curline);
                    return(NULL);
                }
                break;
            case doubledirectout:
                curline->output_is_double = 1;
                /* fall through */
            case directout:
                if (curline->outputfile) {
                    fprintf(stderr,
                            "syntax error: multiple output redirections\n");
                    freeparse(curline);
                    return(NULL);
                }
                if (gettoken(&s, &curline->outputfile) != identifier) {
                    fprintf(stderr, "syntax error in output redirection\n");
                    freeparse(curline);
                    return(NULL);
                }
                break;
            }
        }

        /* cons up just-parsed pipeline component */
        if (argc) {
            *plp = emalloc(sizeof(struct pipeline));
            (*plp)->next = NULL;
            (*plp)->argv = eargvsave(argv, argc);
            (*plp)->isdouble = isdouble;
            plp = &((*plp)->next);
            isdouble = 0;
            argc = 0;
        } else if (tok != eol) {
            fprintf(stderr, "syntax error: null command before `%s'\n",
                    ptok(tok));
            freeparse(curline);
            return(NULL);
        }

        /* ampersanded? */
        if (tok == ampersand)
            curline->isbg = 1;

        /* is this a funny kind of pipe (to the right)? */
        if (tok == doublepipe)
            isdouble = 1;

        /* does this start a new struct parsed_line? */
        if (tok == semicolon || tok == ampersand || tok == doubleampersand || tok == doublebar) {
            curline->next = emalloc(sizeof(struct parsed_line));
            curline = curline->next;

            curline->conntype =
                (tok == semicolon || tok == ampersand) ? CONN_SEQ
                : (tok == doubleampersand) ? CONN_AND
                : CONN_OR;
            curline->inputfile = curline->outputfile = NULL;
            curline->output_is_double = 0;
            curline->isbg = 0;
            curline->pl = NULL;
            curline->next = NULL;
            plp = &(curline->pl);
        }

    } while (tok != eol);
    return(retval);
}


/* (*s) is advanced as we scan; *argp is set iff retval == identifier */
static enum token gettoken(char **s, char **argp)
{
    char *p;

    while (**s && isascii(**s) && isspace(**s))
        (*s)++;
    switch (**s) {
    case '\0':
        return(eol);
    case '<':
        (*s)++;
        return(directin);
    case '>':
        (*s)++;
        if (**s == '&') {
            (*s)++;
            return(doubledirectout);
        }
        return(directout);
    case ';':
        (*s)++;
        return(semicolon);
    case '|':
        if ((*s)[1] == '|') {
            *s += 2;
            return(doublebar);
        }
        (*s)++;
        if (**s == '&') {
            (*s)++;
            return(doublepipe);
        }
        return(verticalbar);
    case '&':
        if ((*s)[1] == '&') {
            *s += 2;
            return(doubleampersand);
        } else {
            (*s)++;
            return(ampersand);
        }
    /* else identifier */
    }

    /* it's an identifier */
    /* find the beginning and end of the identifier */
    p = *s;
    while (**s && isascii(**s) && !isspace(**s) && !strchr("<>;&|", **s))
        (*s)++;
    *argp = estrsavelen(p, *s - p);
    return(identifier);
}


static char *ptok(enum token tok)
{
    switch (tok) {
    case directin:
        return("<");
    case directout:
        return(">");
    case semicolon:
        return(";");
    case verticalbar:
        return("|");
    case ampersand:
        return("&");
    case doubleampersand:
        return("&&");
    case doublebar:
        return("||");
    case doubledirectout:
        return(">&");
    case doublepipe:
        return("|&");
    case eol:
        return("end of line");
    default:
        return(NULL);
    }
}


static void freepipeline(struct pipeline *pl)
{
    if (pl) {
        char **p;
        for (p = pl->argv; *p; p++)
            free(*p);
        free(pl->argv);
        freepipeline(pl->next);
        free(pl);
    }
}


void freeparse(struct parsed_line *p)
{
    if (p) {
        freeparse(p->next);
        if (p->inputfile)
            free(p->inputfile);
        if (p->outputfile)
            free(p->outputfile);
        freepipeline(p->pl);
        free(p);
    }

Builtin.c - 用于 cd 和退出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "fsh.h"
#include "builtin.h"


int builtin_exit(char **argv)
{
    if (argv[1] && argv[2]) /* i.e. argc >= 2 */ {
        fprintf(stderr, "usage: exit [status]\n");
        fflush(stderr);
        return(1);
    } else if (argv[1]) {
        /* "exit ###" */
        exit(atoi(argv[1]));
    } else {
        /* "exit" with no argument */
        exit(laststatus);
    }
}

int builtin_cd(char **argv)
{
    if (argv[1] && argv[2]) {
        fprintf(stderr, "usage: %s dir\n", argv[0]);
        return(1);
    } else if (argv[1]) {
        chdir(argv[1]);
    } else {
        chdir(getenv("HOME"));
    }
    //if (chdir(argv[1])) {
    //    perror(argv[1]);
    //    return(1);
    //}
    return(0);
}

Parse.h - 管道是怎样的

enum connenum {
    CONN_SEQ,  /* sequential commands, i.e. separated by a semicolon */
    CONN_AND,  /* commands joined by '&&' */
    CONN_OR    /* commands joined by '||' */
};

struct pipeline {  /* list of '|'-connected commands */
    char **argv;  /* array ending with NULL */
    struct pipeline *next;  /* NULL if this doesn't pipe into anything */
    int isdouble; /* 1 if we have '|&' i.e. should dup onto 2 */
};

struct parsed_line { /* list of ';' or '&&' or '||' -connected struct pipelines */
    enum connenum conntype;  /* will be CONN_SEQ if this is the first item */
    char *inputfile, *outputfile;  /* NULL for no redirection */
    int output_is_double;  /* output redirection is '>&' rather than '>' */
    struct pipeline *pl;  /* the command(s) */
    int isbg;  /* non-zero iff there is a '&' after this command */
    struct parsed_line *next;   /* connected as specified by next->conntype */
};


extern struct parsed_line *parse(char *s);
extern void freeparse(struct parsed_line *p);

【问题讨论】:

  • 代码无法编译。如果您发布minimal reproducible example,您可能可以获得更多帮助。
  • @terencehill 该程序有多个帮助文件,虽然执行函数本身用的不是最多。我已经用更多代码更新了帖子。
  • 这不完全是 mcve。您应该尝试制作一个重现错误的最小示例。
  • 我会,但我担心它依赖很多,一个最小的例子是无法访问的。唯一能让我关注的方法是提到 execute 中发生的变化,我在其中引入了管道元素,例如 pipe()dup2() 和内部 fork()

标签: c linux bash shell pipe


【解决方案1】:

参考 Creating Pipes in C

参考 GNU Pipe to a Subprocess

如果您真的想深入了解煤层,可能会对您有所帮助。

如果您更喜欢使用 popen

,这可能会对您有所帮助

参考 Pipes the Easy Way using popen

我已经对此进行了测试,它可以执行您想要的操作例如打开 2 个管道(ls 和排序命令)注意我怀疑的 popen("ls", "r") 中的 r 属性可能是上面代码的问题。

例如

如果父母想从孩子那里接收数据,它应该关闭fd1,孩子应该关闭fd0。如果父母要向孩子发送数据,它应该关闭fd0孩子应该关闭fd1

由于描述符在父子节点之间共享,我们应该始终确保关闭我们不关心的管道末端。从技术角度讲,如果管道的不必要的末端没有明确关闭,则永远不会返回 EOF。

管道的文件描述符可能未设置为 Read / Write 或文件描述符以正确的顺序关闭..

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

int main(void)
{
    FILE *pipein_fp, *pipeout_fp;
    char readbuf[80];

    /* Create one way pipe line with call to popen() */
    if (( pipein_fp = popen("ls", "r")) == NULL)
    {
            perror("popen");
            exit(1);
    }

    /* Create one way pipe line with call to popen() */
    if (( pipeout_fp = popen("sort", "w")) == NULL)
    {
            perror("popen");
            exit(1);
    }

    /* Processing loop */
    while(fgets(readbuf, 80, pipein_fp))
            fputs(readbuf, pipeout_fp);

    /* Close the pipes */
    pclose(pipein_fp);
    pclose(pipeout_fp);

    return(0);
}

如果需要Parsing Program Arguments

Getopt:使用 getopt 解析程序选项。

Argp:使用 argp_parse 解析程序选项。

子选项:有些程序需要更详细的选项。

可以帮到你。

一切顺利

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-25
    • 2013-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多