C Minishell添加pipe道

所以我正在制作一个UNIX minishell,并试图添加pipe道,所以我可以做这样的事情:

ps aux | grep dh | grep -v grep | cut -c1-5 

不过,我在缠绕pipe道部分时遇到了麻烦。 我把所有的“|” 字符为0,然后将每行作为普通线路运行。 但是,我试图转移输出和input。 一个命令的input需要是前一个命令的输出,一个命令的输出需要是下一个命令的input。

我正在做这个使用pipe道,但是我不知道在哪里调用pipe()以及在哪里closures它们。 从主处理函数processline()中,我有这样的代码:

 if((pix = findUnquotChar(line_itr, '|'))) { line_itr[pix++] = 0; if(pipe (fd) < 0) perror("pipe"); processline(line_itr, inFD, fd[1], pl_flags); line_itr = &(line_itr[pix]); while((pix = findUnquotChar(line_itr, '|')) && pix < line_len) { line_itr[pix++] = 0; //? if(pipe (fd) < 0) perror("pipe"); processline(line_itr, fd[0], fd[1] pl_flags); line_itr = &(line_itr[pix]); //? close(fd[0]); //? close(fd[1]); } return; } 

所以,我recursion(上面的代码在stream水线)发送命令之间的“|” 由stream程线处理。 你可以看到我在上面的代码注释,我不知道如何使其工作。 processline的第二个和第三个参数分别是inputFD和outputFD,所以我需要处理一个命令,把输出写到一个pipe道,然后在下一个命令中再次调用processline,但是这次前面命令的输出是input。 这似乎并不像它可以工作,因为每次我closuresfd [0]我失去了以前的输出。 我需要两个独立的pipe道,我可以来回翻转吗?

我只是很难看到如何使用单个pipe道,如果你需要任何额外的信息只是问。 以下是整个stream程线function,以便您能够看一下:

http://pastebin.com/YiEdaYdj

编辑:如果有人有一个实现pipe道的shell的例子,我会喜欢一个链接到源,到目前为止我还没有能够find一个谷歌。

编辑2:这是我的困境的一个例子:

 echo a | echo b | echo c 

所以首先我会这样调用shell:

 processline("echo a", 0, fd[1], flags); .... processline("echo b", fd[0], NOT_SURE_GOES_HERE[1], flags); .... processline("echo c", NOT_SURE_GOES_HERE[0], NOT_SURE_EITHER[1], flags); 

每个迭代都会出现一次,正如您所看到的,我无法弄清楚为第二和第三(以此类推)迭代input文件描述符和输出文件描述符要传递什么。

这里有一些适度的通用但简单的代码来执行pipe道,我正在调用pipeline 。 这是一个SSCCE在一个单一的文件中提出,虽然我有文件stderr.hstderr.c作为单独的文件库中的所有我的程序链接。 (实际上,我在“真正的” stderr.cstderr.h更为复杂的function,但这是一个很好的起点。)

代码以两种方式运行。 如果您不提供参数,那么它将运行一个内置pipe道:

 who | awk '{print $1}' | sort | uniq -c | sort -n 

这将统计每个人在系统上login的次数,并以越来越多的会话顺序显示该列表。 或者,您可以调用一系列您想要调用的命令行参数,使用带引号的pipe道'|' (或"|" )分隔命令:

有效:

 pipeline pipeline ls '|' wc pipeline who '|' awk '{print $1}' '|' sort '|' uniq -c '|' sort -n pipeline ls 

无效:

 pipeline '|' wc -l pipeline ls '|' '|' wc -l pipeline ls '|' wc -l '|' 

最后三个调用强制“pipe道作为分隔符”。 该代码不会错误检查每个系统调用; 它检查fork()execvp()pipe() ,但跳过检查dup2()close() 。 它不包括生成的命令的诊断打印; pipeline-x选项将是一个明智的补充,使其打印出它所做的一切。 它也不会以stream水线中最后一个命令的退出状态退出。

请注意,代码从一个分叉的小孩开始。 孩子将成为pipe道中的最后一个进程,但是首先创build一个pipe道,并分stream另一个进程来运行pipe道中的早期进程。 相互recursion函数不太可能成为sorting的唯一方法,但是它们只留下最less的代码重复(在exec_pipeline()exec_pipe_command() )中,早期的代码草案中exec_nth_command()的内容大量重复。

这里的stream程结构是这样的:原始stream程只知道stream水线中的最后一个stream程。 可以重新devise事物,使得原始进程是pipe道中每个进程的父节点,所以原始进程可以单独报告pipe道中每个命令的状态。 我还没有修改代码来允许这个结构; 它会更复杂一点,虽然不是那么可怕。

 /* One way to create a pipeline of N processes */ /* stderr.h */ #ifndef STDERR_H_INCLUDED #define STDERR_H_INCLUDED static void err_setarg0(const char *argv0); static void err_sysexit(char const *fmt, ...); static void err_syswarn(char const *fmt, ...); #endif /* STDERR_H_INCLUDED */ /* pipeline.c */ #include <assert.h> #include <stdio.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> /*#include "stderr.h"*/ typedef int Pipe[2]; /* exec_nth_command() and exec_pipe_command() are mutually recursive */ static void exec_pipe_command(int ncmds, char ***cmds, Pipe output); /* With the standard output plumbing sorted, execute Nth command */ static void exec_nth_command(int ncmds, char ***cmds) { assert(ncmds >= 1); if (ncmds > 1) { pid_t pid; Pipe input; if (pipe(input) != 0) err_sysexit("Failed to create pipe"); if ((pid = fork()) < 0) err_sysexit("Failed to fork"); if (pid == 0) { /* Child */ exec_pipe_command(ncmds-1, cmds, input); } /* Fix standard input to read end of pipe */ dup2(input[0], 0); close(input[0]); close(input[1]); } execvp(cmds[ncmds-1][0], cmds[ncmds-1]); err_sysexit("Failed to exec %s", cmds[ncmds-1][0]); /*NOTREACHED*/ } /* Given pipe, plumb it to standard output, then execute Nth command */ static void exec_pipe_command(int ncmds, char ***cmds, Pipe output) { assert(ncmds >= 1); /* Fix stdout to write end of pipe */ dup2(output[1], 1); close(output[0]); close(output[1]); exec_nth_command(ncmds, cmds); } /* Execute the N commands in the pipeline */ static void exec_pipeline(int ncmds, char ***cmds) { assert(ncmds >= 1); pid_t pid; if ((pid = fork()) < 0) err_syswarn("Failed to fork"); if (pid != 0) return; exec_nth_command(ncmds, cmds); } /* Collect dead children until there are none left */ static void corpse_collector(void) { pid_t parent = getpid(); pid_t corpse; int status; while ((corpse = waitpid(0, &status, 0)) != -1) { fprintf(stderr, "%d: child %d status 0x%.4X\n", (int)parent, (int)corpse, status); } } /* who | awk '{print $1}' | sort | uniq -c | sort -n */ static char *cmd0[] = { "who", 0 }; static char *cmd1[] = { "awk", "{print $1}", 0 }; static char *cmd2[] = { "sort", 0 }; static char *cmd3[] = { "uniq", "-c", 0 }; static char *cmd4[] = { "sort", "-n", 0 }; static char **cmds[] = { cmd0, cmd1, cmd2, cmd3, cmd4 }; static int ncmds = sizeof(cmds) / sizeof(cmds[0]); static void exec_arguments(int argc, char **argv) { /* Split the command line into sequences of arguments */ /* Break at pipe symbols as arguments on their own */ char **cmdv[argc/2]; // Way too many char *args[argc+1]; int cmdn = 0; int argn = 0; cmdv[cmdn++] = &args[argn]; for (int i = 1; i < argc; i++) { char *arg = argv[i]; if (strcmp(arg, "|") == 0) { if (i == 1) err_sysexit("Syntax error: pipe before any command"); if (args[argn-1] == 0) err_sysexit("Syntax error: two pipes with no command between"); arg = 0; } args[argn++] = arg; if (arg == 0) cmdv[cmdn++] = &args[argn]; } if (args[argn-1] == 0) err_sysexit("Syntax error: pipe with no command following"); args[argn] = 0; exec_pipeline(cmdn, cmdv); } int main(int argc, char **argv) { err_setarg0(argv[0]); if (argc == 1) { /* Run the built in pipe-line */ exec_pipeline(ncmds, cmds); } else { /* Run command line specified by user */ exec_arguments(argc, argv); } corpse_collector(); return(0); } /* stderr.c */ /*#include "stderr.h"*/ #include <stdio.h> #include <stdarg.h> #include <errno.h> #include <string.h> #include <stdlib.h> static const char *arg0 = "<undefined>"; static void err_setarg0(const char *argv0) { arg0 = argv0; } static void err_vsyswarn(char const *fmt, va_list args) { int errnum = errno; fprintf(stderr, "%s:%d: ", arg0, (int)getpid()); vfprintf(stderr, fmt, args); if (errnum != 0) fprintf(stderr, " (%d: %s)", errnum, strerror(errnum)); putc('\n', stderr); } static void err_syswarn(char const *fmt, ...) { va_list args; va_start(args, fmt); err_vsyswarn(fmt, args); va_end(args); } static void err_sysexit(char const *fmt, ...) { va_list args; va_start(args, fmt); err_vsyswarn(fmt, args); va_end(args); exit(1); } 

信号和SIGCHLD

POSIX 信号概念部分讨论SIGCHLD:

在SIG_DFL下:

如果默认操作是忽略该信号,那么信号的传送将不会对该过程产生影响。

在SIG_IGN下:

如果SIGCHLD信号的动作设置为SIG_IGN,则调用进程的subprocess终止时不能转换为僵尸进程。 如果调用进程等待其subprocess,并且进程没有未被等待的进入僵尸进程的subprocess,则应阻塞,直到所有的subprocess结束, wait() , waitpid()失败并将errno设置为[ECHILD]

<signal.h>的描述有一个信号默认处置表,对于SIGCHLD,缺省是I(SIG_IGN)。


我在上面的代码中添加了另一个函数:

 #include <signal.h> typedef void (*SigHandler)(int signum); static void sigchld_status(void) { const char *handling = "Handler"; SigHandler sigchld = signal(SIGCHLD, SIG_IGN); signal(SIGCHLD, sigchld); if (sigchld == SIG_IGN) handling = "Ignored"; else if (sigchld == SIG_DFL) handling = "Default"; printf("SIGCHLD set to %s\n", handling); } 

我在调用err_setarg0()之后立即调用它,并在Mac OS X 10.7.5和Linux(RHEL 5,x86 / 64)上报告“Default”。 我通过运行validation其操作:

 (trap '' CHLD; pipeline) 

在这两个平台上,报告“忽略”, pipeline命令不再报告孩子的退出状态; 它没有得到它。

所以,如果程序忽略了SIGCHLD,那么它不会生成任何僵尸,但是会一直等到它的所有子节点终止。 也就是说,直到所有直接的孩子终止为止。 一个过程不能等待其孙辈或更远的后代,也不能等待其兄弟姐妹,也不能等待其祖先。

另一方面,如果SIGCHLD的设置是默认的,则信号被忽略,并且创build僵尸。

这是写这个程序最方便的行为。 corpse_collector()函数有一个循环来收集来自任何孩子的状态信息。 这个代码一次只有一个孩子。 pipe道的其余部分是作为pipe道中最后一个过程的一个孩子(孩子的,孩子的……)运行的。


但是,我遇到了僵尸/尸体的麻烦。 我的老师让我按照相同的方式实现它,因为cmd1不是cmd2的父级:“ cmd1 | cmd2 | cmd3 ”。 除非我告诉我的shell在每个进程( cmd1cmd2cmd3 )上等待,而不是等待最后一个进程( cmd3 ), cmd3整个pipe道在输出到达结束之前closures。 我很难找出一个好的方法来等待他们。 我老师说要用WNOHANG

我不确定我是否理解这个问题。 使用我提供的代码, cmd3cmd2的父代,而cmd2是3命令pipe道中的cmd1的父代(而shell是cmd3的父代),因此shell只能在cmd3等待。 我原来是这样说的:

这里的stream程结构是这样的:原始stream程只知道stream水线中的最后一个stream程。 可以重新devise事物,使得原始进程是pipe道中每个进程的父节点,所以原始进程可以单独报告pipe道中每个命令的状态。 我还没有修改代码来允许这个结构; 它会更复杂一点,虽然不是那么可怕。

如果你的shell能够在stream水线上等待所有三个命令,那么你必须使用另一个组织。

waitpid()描述包括:

pid参数指定一组请求状态的subprocess。 waitpid()函数只能从这个集合中返回一个subprocess的状态:

  • 如果pid等于(pid_t)-1,则为任何subprocess请求状态。 在这方面,waitpid()等价于wait()。

  • 如果pid大于0,则它指定请求状态的单个subprocess的进程ID。

  • 如果pid为0,那么对于进程组ID等于调用进程的subprocess请求状态。

  • 如果pid小于(pid_t)-1,则对于进程组ID等于pid的绝对值的任何subprocess请求状态。

options参数是由包含以下标志的零或多个或多或less的位构成的,在标头中定义:

WNOHANG如果状态不是立即可用于由pid指定的某个subprocess,那么waitpid()函数不应暂停执行调用线程。

这意味着如果你正在使用进程组,并且shell知道哪个进程组正在运行(例如,因为pipe道被第一个进程放入它自己的进程组),那么父进程可以等待适当的孩子终止。

散漫…我想这里有一些有用的信息; 我可能应该有更多的东西在写,但是我的脑子一片空白。