管道功能未正确执行

时间:2016-09-14 18:57:01

标签: c shell pipe piping

我已经构建了以下程序来尝试管道我自己的shell。 StringArray只是我构建的char**。代码运行正常,但是当我输入cat txt.txt | grep a时,没有任何内容打印回屏幕。调试时,我看到代码似乎停止了152(打印输出命令所在的位置),其中pid==0i==0

对于上下文,我在检测到管道后在另一个函数中调用此函数。

void doPipe(StringArray sa) 
{
    printf("In 69\n"); 
    int filedes[2]; // pos. 0 output, pos. 1 input of the pipe
    int filedes2[2];

    int num_cmds = 0;

    char *command[256];

    pid_t pid;

    int err = -1;
    int end = 0;

    // Variables used for the different loops
    int i = 0;
    int j = 0;
    int k = 0;
    int l = 0;

    // First we calculate the number of commands (they are separated
    // by '|')
    while (sa[l] != NULL){
        if (strcmp(sa[l],"|") == 0){
            num_cmds++;
        }
        l++;
    }
    num_cmds++;

    // Main loop of this method. For each command between '|', the
    // pipes will be configured and standard input and/or output will
    // be replaced. Then it will be executed
    while (sa[j] != NULL && end != 1){
        k = 0;
        // We use an auxiliary array of pointers to store the command
        // that will be executed on each iteration
        while (strcmp(sa[j],"|") != 0){
            command[k] = sa[j];
            j++;    
            if (sa[j] == NULL){
                // 'end' variable used to keep the program from entering
                // again in the loop when no more arguments are found
                end = 1;
                k++;
                break;
            }
            k++;
        }
        // Last position of the command will be NULL to indicate that
        // it is its end when we pass it to the exec function
        command[k] = NULL;
        j++;        
        printf("In 121\n"); 

        // Depending on whether we are in an iteration or another, we
        // will set different descriptors for the pipes inputs and
        // output. This way, a pipe will be shared between each two
        // iterations, enabling us to connect the inputs and outputs of
        // the two different commands.
        if (i % 2 != 0){
            pipe(filedes); // for odd i
        }else{
            pipe(filedes2); // for even i
        }

        pid=fork();

        if(pid==-1){            
            if (i != num_cmds - 1){
                if (i % 2 != 0){
                    close(filedes[1]); // for odd i
                }else{
                    close(filedes2[1]); // for even i
                } 
            }           
            printf("Child process could not be created\n");
            return;
        }
        if(pid==0){
            printf("In 148\n"); 

            // If we are in the first command
            if (i == 0){
                printf("In 152\n"); 

                dup2(filedes2[1], STDOUT_FILENO);
            }
            // If we are in the last command, depending on whether it
            // is placed in an odd or even position, we will replace
            // the standard input for one pipe or another. The standard
            // output will be untouched because we want to see the 
            // output in the terminal
            else if (i == num_cmds - 1){
                printf("In 162\n"); 

                if (num_cmds % 2 != 0){ // for odd number of commands
                    dup2(filedes[0],STDIN_FILENO);
                    printf("In 166\n"); 

                }else{ // for even number of commands
                    dup2(filedes2[0],STDIN_FILENO);
                    printf("In 166\n"); 

                }
            // If we are in a command that is in the middle, we will
            // have to use two pipes, one for input and another for
            // output. The position is also important in order to choose
            // which file descriptor corresponds to each input/output
            }else{ // for odd i
                if (i % 2 != 0){
                    dup2(filedes2[0],STDIN_FILENO); 
                    dup2(filedes[1],STDOUT_FILENO);
                }else{ // for even i
                    dup2(filedes[0],STDIN_FILENO); 
                    dup2(filedes2[1],STDOUT_FILENO);                    
                } 
            }

            if (execvp(command[0],command)==err){
                kill(getpid(),SIGTERM);
            }       
        }

        // CLOSING DESCRIPTORS ON PARENT
        if (i == 0){
            close(filedes2[1]);
        }
        else if (i == num_cmds - 1){
            if (num_cmds % 2 != 0){                 
                close(filedes[0]);
            }else{                  
                close(filedes2[0]);
            }
        }else{
            if (i % 2 != 0){                    
                close(filedes2[0]);
                close(filedes[1]);
            }else{                  
                close(filedes[0]);
                close(filedes2[1]);
            }
        }

        waitpid(pid,NULL,0);

        i++;    
    }


}

1 个答案:

答案 0 :(得分:4)

您的一个重大问题可能是在管道构造的每次迭代中执行waitpid。等待应该在结束时完成(记住列表中的pids)。

我在理解你的代码时遇到了一些困难,所以我做了一些简化和清理工作。特别是,在任何地方做if (i % 2 ...)都会让事情变得更难。

我已经清理并修复了代码。我添加了一个结构,以便更容易管理[请原谅无偿的样式清理]:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

typedef struct {
    int pipe_fildes[2];
} pipectl_t;

#define CLOSEME(_fd) \
    do { \
        close(_fd); \
        _fd = -1; \
    } while (0)

void
doPipe(char **sa)
{
    pipectl_t pipes[2];
    pipectl_t *pipein;
    pipectl_t *pipeout;
    pipectl_t *pipetmp;

    int num_cmds = 0;

    char *command[256];
    pid_t pidlist[256];

    pid_t pid;

    int err = -1;
    int end = 0;

    // Variables used for the different loops
    int icmd = 0;
    int j = 0;
    int k = 0;
    int l = 0;

    // First we calculate the number of commands (they are separated
    // by '|')
    for (int l = 0;  sa[l] != NULL;  ++l) {
        if (strcmp(sa[l], "|") == 0)
            num_cmds++;
    }
    num_cmds++;

    for (int ipipe = 0;  ipipe <= 1;  ++ipipe) {
        pipes[ipipe].pipe_fildes[0] = -1;
        pipes[ipipe].pipe_fildes[1] = -1;
    }

    pipein = &pipes[0];
    pipeout = &pipes[1];

    // Main loop of this method. For each command between '|', the
    // pipes will be configured and standard input and/or output will
    // be replaced. Then it will be executed
    while (sa[j] != NULL && end != 1) {
        // We use an auxiliary array of pointers to store the command
        // that will be executed on each iteration
        k = 0;
        while (strcmp(sa[j], "|") != 0) {
            command[k] = sa[j];
            j++;
            k++;
            if (sa[j] == NULL) {
                // 'end' variable used to keep the program from entering
                // again in the loop when no more arguments are found
                end = 1;
                break;
            }
        }

        // Last position of the command will be NULL to indicate that
        // it is its end when we pass it to the exec function
        command[k] = NULL;

        j++;

        // swap input and output, so previous child's output becomes the new
        // child's input
        // NOTE: by doing this here, in one place, we eliminate all the i % 2
        // if statements
        pipetmp = pipein;
        pipein = pipeout;
        pipeout = pipetmp;

        // are we the last command?
        int lastflg = (icmd == (num_cmds - 1));

        // last command does _not_ have an output pipe, so don't create one
        if (! lastflg)
            pipe(pipeout->pipe_fildes);

        pid = fork();

        // NOTE: fork failure almost never happens and is fatal
        if (pid == -1) {
            printf("Child process could not be created\n");
            return;
        }

        // process child
        if (pid == 0) {
            // NOTE: after we've dup'ed a file descriptor, we close it

            // first command does _not_ have a pipe for input
            if (icmd > 0)
                dup2(pipein->pipe_fildes[0],STDIN_FILENO);
            CLOSEME(pipein->pipe_fildes[0]);

            // last command does _not_ have a pipe for output
            if (! lastflg)
                dup2(pipeout->pipe_fildes[1],STDOUT_FILENO);
            CLOSEME(pipeout->pipe_fildes[1]);

            // close the parent sides of the pipes (in this child)

            // close previous child's output descriptor (the feed for our input)
            CLOSEME(pipein->pipe_fildes[1]);

            // close next child's input descriptor (our feed for its input)
            CLOSEME(pipeout->pipe_fildes[0]);

            if (execvp(command[0], command) == err) {
#if 0
                kill(getpid(), SIGTERM);
#else
                exit(1);
#endif
            }
        }

        // close all input descriptors for _this_ child
        CLOSEME(pipein->pipe_fildes[0]);
        CLOSEME(pipein->pipe_fildes[1]);

        // close output side of _this_ child's output pipe [which becomes next
        // child's input pipe]
        CLOSEME(pipeout->pipe_fildes[1]);

        pidlist[icmd] = pid;

        icmd++;
    }

    // wait for all pids _after_ the entire pipeline is constructed
    for (int icmd = 0;  icmd < num_cmds;  ++icmd)
        waitpid(pidlist[icmd], NULL, 0);
}

// main -- main program
int
main(int argc,char **argv)
{
    char *cp;
    char *bp;
    char buf[1000];
    char **av;
    char *avlist[256];

    --argc;
    ++argv;

    for (;  argc > 0;  --argc, ++argv) {
        cp = *argv;
        if (*cp != '-')
            break;

        switch (cp[1]) {
        default:
            break;
        }
    }

    while (1) {
        printf("> ");
        fflush(stdout);

        cp = fgets(buf,sizeof(buf),stdin);
        if (cp == NULL)
            break;

        av = avlist;
        bp = buf;
        while (1) {
            cp = strtok(bp," \t\r\n");
            bp = NULL;

            if (cp == NULL)
                break;

            *av++ = cp;
        }
        *av = NULL;

        doPipe(avlist);
    }

    return 0;
}

<强>更新

  

当我运行此代码时,相同的命令cat txt.txt | grep a仅显示为执行第一个命令,而不是管道之后的第二个命令。 (它在txt文件中输出但没有grep)

我在发布之前测试了整个程序。我只是使用cat/grep命令重新测试。它奏效了,但这是我的计划不变。

  

为什么会发生这种情况的任何想法?我在我的代码中实现了你的doPipe方法,并传入了我的StringArray sa,它也只是一个char **。

我的建议是:

  1. 验证我的未更改版本是否适合您。
  2. gdb上使用doPipe断点并查看参数。对于这两个程序,它们应该是相同的。
  3. 如果StringArray确实是char **,请在您的版本中将其替换,以确保它没有任何区别。那是void doPipe(char **sa)并查看您的代码是否仍然编译。在断点处的gdb中,您应该可以在两个程序上执行ptype sa
  4. StringArray看起来有点&#34; Java-esque&#34;对我来说:-)我避免使用它,特别是因为execvp需要char **
  5. 确认sa已正确NULL终止。如果它不是管道中的最后一个命令可能是假的/垃圾,那么对失败的execvp的错误检查不是那么健全。
  6. 验证num_cmds是否相同。
  7. 试试cat txt.txt | grep a | sed -e s/a/b/。如果您获得catgrep,而不是sed,则表示num_cmds不正确
  8. 验证调用者对缓冲区的解析是否将"|"置于单独的标记中。也就是说,此代码适用于cat txt.txt | grep a,但可以使用:cat txt.txt|grep a
  9. 更新#2:

    顺便说一句,如果您的管道代码仍然无效(例如,最后一个命令未执行),请检查最后一个令牌是否有换行符(即换行符不是&# 39;正确剥离。

      

    我已经尝试了所有这些,但仍然无法使用我的重定向代码来解决这个问题。从本质上讲,我对此代码中应该检查的位置感到困惑&#39;&lt;&#39;或&#39;&gt;&#39;

    进行常规解析以支持重定向(例如<>),管道(例如|),每行多个命令(例如;),嵌入式子-shells(例如(echo the date is ; date)和分离的工作(例如&)可能需要一些小心,你需要一个多层次的方法。

    我怀疑在你获得管道和/或重定向工作之后,你的任务就是实现更多的shell语法。我以前做过这件事,所以,不是你把它弄得零碎,这就是你需要做的......

    您需要扫描输入缓冲区char-by-char并将令牌保存到&#34;令牌&#34;也有类型的struct。您需要这些结构的链表。更多内容如下。

    当您遇到带引号的字符串时,您需要删除引号:"abc" - &gt; abc,注意转义引号:"ab\"c - &gt; ab"c

    另外,你必须要小心所谓的字符串,这些字符串与[perl调用]&#34;&#34; bareword&#34;字符串:echo abc。如果我们有abc"d ef"ghi,则需要将其连接成一个字符串标记:abcd efghi

    还必须考虑重定向器上的反斜杠。 echo abc > def是一个重定向,可将abc放入文件def。但是,echo abc \> def应该直接输出abc > def到stdout。另一方面反斜杠&#34;标点符号&#34;很相似。

    您还必须处理标点符号 周围有空格的事实。也就是说,echo abc>def必须像处理echo abc > def一样处理。

    另外,引用字符串中的标点符号应该被视为上面的转义。也就是说,echo abc ">" def 重定向,[再次]应该被视为一个简单的命令。

    此外,如果当前行以反斜杠结束(例如\<newline>),这意味着下一行是&#34;继续&#34;线。你应该删除反斜杠和换行符。然后,读取另一行并继续构建令牌列表。

    此外,虽然&可以用于分离的作业,例如:date &,但它也可以是重定向的一部分,如gcc -o myshell myshell.c 2>&1 >logfile

    好的,为了管理所有这些,我们需要令牌和令牌结构的类型:

    // token types
    typedef enum {
        TOKEN_NORMAL,                       // simple token/string
        TOKEN_QUO1,                         // quoted string
        TOKEN_QUO2,                         // quoted string
        TOKEN_SEMI,                         // command separater (e.g. ;)
        TOKEN_OREDIR,                       // output redirector (e.g. >)
        TOKEN_IREDIR,                       // input redirector (e.g. <)
        TOKEN_PIPE,                         // pipe separater (e.g. |)
        TOKEN_AMP                           // an & (can be detach or redirect)
    } toktype_t;
    
    // token control
    typedef struct token token_t;
    struct token {
        token_t *tok_next;                  // forward link
        token_t *tok_prev;                  // backward link
        toktype_t tok_type;                 // token type
        char tok_str[256];                  // token value
    };
    
    // token list
    typedef struct tlist tlist_t;
    struct token {
        tlist_t *tlist_next;                // forward link
        tlist_t *tlist_prev;                // backward link
    
        token_t *tlist_head;                // pointer to list head
        token_t *tlist_tail;                // pointer to list tail
    };
    

    最初,在解析输入行[注意延续]之后,我们只有一个tlist

    如果列表中包含;个分隔符,我们会将它们拆分为创建子列表。然后我们循环访问子列表并按顺序执行命令。

    查看子命令时,如果它以&结尾,则必须以分离方式运行该命令。我们注意到并将其从列表背面弹出。

    好的,现在我们有一个可能是以下形式的列表:

    cat < /etc/passwd | grep root | sed -e s/root/admin/ > /tmp/out
    

    现在,我们对|进行了进一步拆分,因此我们有一个包含三个元素的列表:

    cat < /etc/passwd
    grep root
    sed -e s/root/admin/ > /tmp/out
    

    实际上,每个&#34;线&#34;是tlist,这是列表的二维列表:

    list_of_tlists:
      |
      |
    tlist[0] --> cat --> < --> /etc/passwd
      |
      |
    tlist[1] --> grep --> root
      |
      |
    tlist[2] --> sed --> -e --> s/root/admin/ --> > /tmp/out
    

    在我们创建管道时,我们会注意重定向,并根据需要执行open而不是pipe

    好的,这是摘要。

    请在此处查看我的答案:Implementing input/output redirection in a Linux shell using C以获得完整且完整的实施。

    在该页面上,有代码可以进行重定向。它可能适用于通过将代码与我在此处发布的代码合并来包含管道。

    OP要求帮助进行重定向管道。

    旁注:当时,有一连串的shell实施问题。所以,我最终制作了一个完整的shell,可以完成所有的工作。但是,该版本太大而无法在SO上发布。因此,在该页面中,找到我发布的pastebin链接。它有完整的源代码。它可以下载,构建和运行。

    您可能不想直接使用那个代码,但它应该会给您一些想法。此外,完整版可能会做的事情与我上面描述的有所不同。