管道function无法正常执行
我已经构建了以下程序来尝试管道我自己的shell。 StringArray
只是我构建的char**
。 代码运行正常,但是当我输入cat txt.txt | grep a
cat txt.txt | grep a
,没有任何东西打印回屏幕。 在调试时,我看到代码似乎停止了152(打印输出命令所在的位置),其中pid==0
和i==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 **。
我的建议是:
- validation我的未更改版本是否适合您。
- 在
doPipe
上使用gdb
断点并查看参数。 对于这两个程序,它们应该是相同的。 - 如果
StringArray
是真正的char **
,请在您的版本中替换它以确保它没有任何区别。 这是void doPipe(char **sa)
并查看您的代码是否仍然编译。 在断点处的gdb
中,您应该能够在两个程序上执行ptype sa
-
StringArray
对我看起来有点“Java-esque”:-)我会避免它,特别是因为execvp
一个char **
- validation
sa
是否正确地以NULL
结尾。 如果它不是管道中的最后一个命令可能是伪造/垃圾,并且失败的execvp
的错误检查不是那么健壮。 - validation
num_cmds
是否相同。 - 试试
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/
。 如果你得到cat
和grep
,而不是sed
,这意味着num_cmds
不正确 - 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链接。 它有完整的源代码。 它可以下载,构建和运行。
您可能不想直接使用该代码,但它应该给您一些想法。 此外,完整版可能会做的事情与我上面描述的有所不同。