管道功能无法正常执行 [英] Pipe function not executing properly

查看:107
本文介绍了管道功能无法正常执行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have constructed the following program to try to pipe in my own shell. A StringArray is simply a char** I have constructed. The code runs fine, but when I put in cat txt.txt | grep a, nothing prints back to the screen. When debugging, I saw that the code seems to stop around like 152 (where the print-out command is), where pid==0 and i==0.

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

For context, I'm calling this function in another function after a pipe has been detected.

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


}

推荐答案

您的一个大问题可能是在管道构造的每次迭代中执行waitpid.等待应该在最后完成(记住列表中的pid).

One of your big problems may be doing waitpid on each iteration of the pipeline construction. The waiting should be done at the end (remembering the pids in a list).

我在理解您的代码时遇到了一些困难,因此进行了一些简化和清理.特别是,在任何地方执行if (i % 2 ...)都会使事情变得更困难.

I had some difficulty understanding your code, so I did some simplification and cleanup. In particular, doing if (i % 2 ...) everywhere made things harder.

我已经清理并修复了代码.我添加了一个结构,使事情更易于管理[请原谅免费的样式清理]:

I've cleaned up and fixed the code. I added a struct to make things easier to manage [please pardon the gratuitous style cleanup]:

#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)

When I run this code, the same command cat txt.txt | grep a only appears to do the first command, and not the second after the pipe. (It cats out the txt file but does not grep)

我在发布之前测试了整个程序.我只是使用cat/grep命令进行了重新测试.它有效,但是那是我的程序不变.

I tested the entire program before I posted. I just retested using a cat/grep command. It worked, but that was my program unchanged.

有什么想法可能会发生这种情况吗?我在代码中实现了doPipe方法,并将其传递给StringArray sa,它也只是一个char **.

Any ideas why this could be happening? I implemented your doPipe method in my code and passed in my StringArray sa which is just a char ** as well.

我的建议是:

  1. 请确认我的不变版本适用于您.
  2. doPipe上使用gdb断点并查看参数.对于这两个程序,它们应该相同.
  3. 如果StringArray确实是char **,请在您的版本中将其替换以确保没有区别.那是void doPipe(char **sa)并查看您的代码是否仍在编译.在断点的gdb中,您应该能够在两个程序上都执行ptype sa
  4. 在我看来StringArray看起来有点像Java风格:-)我会避免使用它,尤其是在这里,因为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. 一起使用
  1. Verify that my unchanged version works for you.
  2. Using gdb breakpoint on doPipe and look at the arguments. For both programs, they should be the same.
  3. If StringArray is truly char **, replace it in your version to ensure it makes no difference. That is void doPipe(char **sa) and see if your code still compiles. In gdb at the breakpoint, you should be able to do ptype sa on both programs
  4. The StringArray looks a bit "Java-esque" to me :-) I'd avoid it, particularly here since execvp wants a char **
  5. Verify that sa is properly NULL terminated. If it isn't the last command in the pipeline may be bogus/garbage and the error checking for a failed execvp isn't that robust.
  6. Verify that num_cmds is the same.
  7. Try cat txt.txt | grep a | sed -e s/a/b/. If you get the cat and grep, but not the sed, this means num_cmds is not correct
  8. Verify that caller's parsing of the buffer puts the "|" in a separate token. That is, this code works with cat txt.txt | grep a but it will not work with: cat txt.txt|grep a


更新#2:

顺便说一句,如果您的管道代码仍然不起作用(例如,最后一条命令未执行 ),请检查最后一个令牌是否带有换行符(即,换行符未被剥离)正确).

BTW, if your pipe code still isn't working (e.g. the last command is not executed), check to see if the last token has newline on it (i.e. the newline wasn't stripped correctly).

我已经尝试了所有这些方法,但是仍然无法获得重定向代码来处理此问题.本质上,我对于应该在代码中的何处检查<"感到困惑或'>'

I've tried all of this but still can't get my redirection code to work with this. Essentially, I'm confused as to where in this code I should be checking for '<' or '>'

进行一般解析以支持重定向(例如<>),管道(例如|),每行多个命令(例如;),嵌入式子外壳(例如(echo the date is ; date),和独立工作(例如&)可能需要一些注意,并且您需要多层次的方法.

Doing the general parsing to support redirection (e.g. < or >), pipes (e.g. |), multiple commands per line (e.g. ;), embedded sub-shells (e.g. (echo the date is ; date), and detached jobs (e.g. &) can require a bit of care and you need a multilevel approach.

我怀疑在使管道和/或重定向正常工作之后,您将承担实现更多shell语法的任务.我之前已经做过此事,因此,与其您零零敲碎,不如这是您需要做的...

I suspect that after you get pipes and/or redirection working, you're tasked with implementing even more shell syntax. I've done this before, so, rather than you figuring it out piecemeal, here is what you'll need to do ...

您需要逐个字符扫描输入缓冲区,并将令牌保存到也具有类型的令牌"结构中.您将需要这些结构的链接列表.详情请见下文.

You'll need to scan the input buffer char-by-char and save off tokens into a "token" struct that also has a type. You'll need a linked list of those structs. More on this below.

当遇到带引号的字符串时,您需要去除引号:"abc"-> abc,要注意转义的引号:"ab\"c-> ab"c.

When you encounter a quoted string, you'll need to strip off the quotes: "abc" --> abc, being mindful of escaped quotes: "ab\"c --> ab"c.

此外,您还必须注意引号字符串与[bareword]字符串邻接:[c42>调用]:echo abc.如果我们有abc"d ef"ghi,则需要将其连接到单个字符串令牌中:abcd efghi

Also, you have to be careful about quoted strings abutting [what perl calls] "bareword" strings: echo abc. If we have abc"d ef"ghi, this needs to be concatenated into a single string token: abcd efghi

还必须考虑重定向器上的反斜杠. echo abc > def是将abc放入文件def中的重定向.但是,echo abc \> def应该只将abc > def从字面上输出到stdout.其他标点符号"上的反斜杠类似.

Backslashes on redirectors must also be accounted for. echo abc > def is a redirection that will put abc into the file def. But, echo abc \> def should just output abc > def literally to stdout. Backslash on the other "punctuation" is similar.

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

You'll also have to handle the fact that punctuation doesn't have to have whitespace around it. That is, echo abc>def has to be handled just as if it were echo abc > def.

此外,带引号的字符串内的标点符号应被视为上面的转义符.也就是说,echo abc ">" def不是 重定向,并且[再次]应该被视为简单命令.

Also, punctuation inside a quoted string should be treated as if it were escaped above. That is, echo abc ">" def is not a redirection and [again] should be treated as a simple command.

此外,如果当前行以反斜杠结尾(例如\<newline>),则表示下一行是继续"行.您应该删除反斜杠和换行符.然后,读另一行并继续建立令牌列表.

Also, if the current line ends in a backslash (e.g. \<newline>), this means that the next line is a "continuation" line. You should strip the backslash and the newline. Then, read another line and continue to build up the token list.

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

Further, while & can be for detached jobs, as in: date &, it can also be part of a redirection, as in gcc -o myshell myshell.c 2>&1 >logfile

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

Okay, so to manage all this we need types for tokens and a token struct:

// 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.

Initially, after parsing an input line [being mindful of the continuations], we have a single tlist.

如果列表中包含;个分隔符,我们将它们拆分以创建子列表.然后,我们在子列表上循环并按顺序执行命令.

If the list has ; separaters in it, we split on them to create sublists. We then loop on the sublists and execute the commands in order.

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

When looking at a subcommand, if it ends in &, the command must be run detached. We note that and pop it off the back of the list.

好的,现在我们有了一个清单,形式可能是:

Okay, now we have a list that might be of the form:

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

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

Now, we do a further split on the | so we have a list that has three elements:

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

实际上,每条线"都是tlist,这是列表的二维列表:

Actually, each of those "lines" is a tlist and this is a two dimensional list of lists:

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

在创建管道时,请注意重定向并根据需要执行文件open而不是pipe.

As we create the pipeline, we note the redirections and do file open instead of pipe as needed.

好的,那是抽象的.

在这里查看我的答案:使用C 在Linux shell中实现输入/输出重定向,以实现完整而完整的实现.

See my answer here: Implementing input/output redirection in a Linux shell using C for a full and complete implementation.

在该页面上,有仅用于进行重定向的代码.通过将代码与我在此处发布的代码合并,可以将其修改为包括管道.

On that page, there is code for just doing redirections. It could probably be adapted to include pipes by merging that code with the code I've posted here.

那个OP在进行重定向管道时寻求帮助.

That OP asked for help in doing redirections and pipes.

旁注:那时,出现了大量的shell实现问题.因此,我最终生产了一个可以完成几乎所有工作的完整外壳.但是,该版本太大,无法发布在SO上.因此,在该页面中,找到我发布的pastebin链接.它具有完整的源代码.可以下载,构建和运行它.

Side note: At that time, there was a spate of shell implementation questions. So, I ended up producing a full shell that does pretty much everything. But, that version was too large to post on SO. So, in that page, find the pastebin link I posted. It has full source code. It can be downloaded, built, and run.

您可能不想直接使用那个代码,但是它应该给您一些想法.另外,完整版的功能可能与我上面所述的有所不同.

You may not want to use that code directly, but it should give you some ideas. Also, the full version may do things a little bit differently than what I've described above.

这篇关于管道功能无法正常执行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆