管道function无法正常执行

我已经构建了以下程序来尝试管道我自己的shell。 StringArray只是我构建的char** 。 代码运行正常,但是当我输入cat txt.txt | grep a 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++; } } 

您的一个重大问题可能是在管道构造的每次迭代中都要做waitpid 。 等待应该在最后完成(记住列表中的pids)。

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

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

 #include  #include  #include  #include  #include  #include  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 cat txt.txt | grep a只显示第一个命令,而不是管道之后的第二个命令。 (它把txt文件搞砸了,但没有grep)

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

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

我的建议是:

  1. validation我的未更改版本是否适合您。
  2. doPipe上使用gdb断点并查看参数。 对于这两个程序,它们应该是相同的。
  3. 如果StringArray是真正的char ** ,请在您的版本中替换它以确保它没有任何区别。 这是void doPipe(char **sa)并查看您的代码是否仍然编译。 在断点处的gdb中,您应该能够在两个程序上执行ptype sa
  4. StringArray对我看起来有点“Java-esque”:-)我会避免它,特别是因为execvp一个char **
  5. validationsa是否正确地以NULL结尾。 如果它不是管道中的最后一个命令可能是伪造/垃圾,并且失败的execvp的错误检查不是那么健壮。
  6. validationnum_cmds是否相同。
  7. 试试cat txt.txt | grep a | sed -es/a/b/ cat txt.txt | grep a | sed -es/a/b/ cat txt.txt | grep a | sed -es/a/b/ 。 如果你得到catgrep ,而不是sed ,这意味着num_cmds不正确
  8. validation调用者对缓冲区的解析是否为"|" 在一个单独的标记。 也就是说,此代码适用于cat txt.txt | grep a cat txt.txt | grep a但它不能用于: cat txt.txt|grep a

更新#2:

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

我已经尝试了所有这些,但仍然无法让我的重定向代码与此一起工作。 从本质上讲,我很困惑这个代码中我应该检查’<'或'>‘

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

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

您需要扫描输入缓冲区char-by-char并将标记保存到也具有类型的“标记”结构中。 你需要一个这些结构的链表。 更多关于此的信息。

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

此外,你必须小心所谓的字符串邻接[ perl calls]“bareword”字符串: echo abc 。 如果我们有abc"d ef"ghi ,则需要将其连接成单个字符串标记: abcd efghi

还必须考虑重定向器上的反斜杠。 echo abc > def是一个将abc放入文件def的重定向。 但是, echo abc \> def应该只输出abc > def字面到stdout。 另一个“标点符号”的反斜杠是类似的。

你还必须处理这样一个事实,即标点符号不一定要有空格。 也就是说,必须处理echo abc>def ,就好像它是echo abc > def

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

此外,如果当前行反斜杠\ (例如\ ),则表示下一行是“延续”行。 你应该去掉反斜杠和换行符。 然后,读取另一行并继续构建令牌列表。

此外,虽然&可以用于分离的作业,如: 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 (eg ;) TOKEN_OREDIR, // output redirector (eg >) TOKEN_IREDIR, // input redirector (eg <) TOKEN_PIPE, // pipe separater (eg |) 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

如果清单有; 在它的分隔符中,我们将它们分开以创建子列表。 然后我们循环访问子列表并按顺序执行命令。

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

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

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

现在,我们对|进行进一步分割 所以我们有一个包含三个元素的列表:

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

实际上,每个“行”都是一个tlist ,这是一个二维列表:

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

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

好的,那是抽象的。

请参阅我的回答: 使用C在Linux shell中实现输入/输出重定向,以实现完整和完整的实现。

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

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

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

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