使用fork()和exec()在C中执行命令行管道

我询问了我的代码,答案是不正确的。 在这种情况下perror使用现在我想知道我如何调整和改进,以便我不再有这些错误?

execvp不会返回,除非发生错误,所以如果一切正常,封闭函数将永远不会返回。

由于先前的循环,值’i’已经超过’cmd’中数组的末尾,因此’cmd [i] .argv [0]不正确。

cmd不是struct命令的数组,因此不应编入索引

cmd.argv中的第一个条目是指向最后一个条目为NULL的数组的指针。 execvp将在该(并且只有那个)数组上工作,因此所有其他指向数组的指针都将被忽略

代码中存在大量错误。 例如,第一次通过fork_pipe()’in’中的循环包含垃圾。 传递给execvp()的第二个参数需要是一个指向字符串的指针,带有一个最终的NULL指针。 缺少最后的NULL指针,还有很多问题

#include  #include  #include  #include  #include  #include  struct command { const char **argv; }; /* Helper function that spawns processes */ int spawn_proc (int in, int out, struct command *cmd) { pid_t pid; if ((pid = fork ()) == 0) { if (in != 0) { /*if (dup2(in, 0) == -1) { perror("dup2 failed"); exit(1); }*/ dup2 (in, 0); close (in); } if (out != 1) { dup2 (out, 1); close (out); } if (execvp(cmd->argv [0], (char * const *)cmd->argv) < 0) { perror("execvp failed"); exit(1); } } else if (pid < 0) { perror("fork failed"); exit(1); } return pid; } /* Helper function that forks pipes */ int fork_pipes (int n, struct command *cmd) { int i; int in, fd [2]; for (i = 0; i < n - 1; ++i) { pipe (fd); spawn_proc (in, fd [1], cmd + i); close (fd [1]); in = fd [0]; } dup2 (in, 0); /*return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);*/ if (execvp (cmd [i].argv [0], (char * const *)cmd [i].argv)  1) { /* I'd like an argument */ if (strncmp(argv[1], "cd", 2) && strncmp(argv[1], "exit", 2)) { char *tmp; int len = 1; for( i=1; i<argc; i++) { len += strlen(argv[i]) + 2; } tmp = (char*) malloc(len); tmp[0] = '\0'; int pos = 0; for( i=1; i<argc; i++) { pos += sprintf(tmp+pos, "%s%s", (i==1?"":"|"), argv[i]); } const char *printenv[] = { "printenv", 0}; const char *grep[] = { "grep", "-E", tmp, NULL}; const char *sort[] = { "sort", 0 }; const char *less[] = { "less", 0 }; struct command cmd [] = { {printenv}, {grep}, {sort}, {less} }; return fork_pipes (4, cmd); free(tmp); } else if (! strncmp(argv[1], "cd", 2)) { /* change directory */ printf("change directory to %s\n" , argv[2]); chdir(argv[2]); } else if (! strncmp(argv[1], "exit", 2)) { /* change directory */ printf("exit\n"); exit(0); } } exit(0); } 

将评论转移到(部分)答案中。

  • 您不需要对execvp()进行测试(如果它返回,则失败),但您确实需要错误报告并在其后退出调用。

  • cmd不是数组’评论似乎是假的; 在fork_pipes()里面,它是一个数组。 它不用作spawn_proc()的数组。

  • 我认为评论’ cmd.argv的第一个条目是指向最后一个条目为NULL的数组的指针。 execvp将在那个(并且只有那个)数组上运行,因此所有其他指向数组的指针都将被忽略’也是假的。 我认为他们忽略了你正在创建一个struct命令数组。

  • 我认为评论’由于先前的循环, i已经超过cmd数组末尾的值,因此cmd[i].argv[0]不正确’是不正确的,因为循环是for (i = 0; i < n - 1; i++)所以i在循环之后是n-1 ,并且数组cmd具有要解决的元素0..n-1

  • 但是,第一次调用spawn_proc() in的值确实是垃圾。 你可以简单地将它设置为0STDIN_FILENO )并且没问题,但你需要validation它。 但关于execvp()的第二个参数的评论是特殊的 - 演员应该不存在,但是否则代码对我来说看起来不错。 我应该补充一点,我还没有运行任何编译器,所以我到目前为止所说的任何东西都要由编译器来纠正。 但是,我不是随便做分析......你是用你的编译器编译设置挑剔的: gcc -Wall -Wextra -Werror作为最低限度(我使用更多的选项!)?

  • fork_pipes() ,对execvp()else子句的if测试很奇怪。 你只需要调用execvp()perror()exit()


以上这些评论基本上是准确的。 这是一些修改过的代码,但修改主要是装饰性的。 err_syserr()函数基于我用于报告错误的内容。 它是一个varargs函数,也报告系统错误。 它比perror()更好,因为(a)它可以更全面地格式化并且(b)它退出。

我收到的汇编警告如下:

 ft13.c: In function 'spawn_proc': ft13.c:45:9: error: passing argument 2 of 'execvp' from incompatible pointer type [-Werror] execvp(cmd->argv[0], cmd->argv); ^ In file included from ft13.c:6:0: /usr/include/unistd.h:440:6: note: expected 'char * const*' but argument is of type 'const char **' int execvp(const char *, char * const *); 

解决这些问题的最简单方法是将const放在struct command中的正确位置,并从各个命令的参数列表中删除const

其他变化更具外观性而非实质性(未初始化是唯一需要修复的严重错误)。 我使用了我的错误报告代码,并检查了一些额外的系统调用(例如, dup2() ),并清理了execvp()和错误报告。 我在一般代码之前移动了exitcd的测试,以避免重复测试。 另外,你使用的是strncmp()exit测试只是查看ex ,但ex是系统命令...使用strcmp() 。 我在条件中使用strcmp(x, y) == 0 ; 正在使用的关系运算符模仿我正在测试的关系运算(所以strcmp(x, y) >= 0测试x大于或等于y等)。

现代POSIX不需要#include 作为包含。 其他标题包含必要的内容。

资料来源: ft13.c

编译为ft13

 #include  #include  #include  #include  #include  #include  struct command { char * const *argv; }; static _Noreturn void err_syserr(char *fmt, ...) { int errnum = errno; va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); if (errnum != 0) fprintf(stderr, "(%d: %s)\n", errnum, strerror(errnum)); exit(EXIT_FAILURE); } /* Helper function that spawns processes */ static int spawn_proc(int in, int out, struct command *cmd) { pid_t pid; if ((pid = fork()) == 0) { if (in != 0) { if (dup2(in, 0) < 0) err_syserr("dup2() failed on stdin for %s: ", cmd->argv[0]); close(in); } if (out != 1) { if (dup2(out, 1) < 0) err_syserr("dup2() failed on stdout for %s: ", cmd->argv[0]); close(out); } fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd->argv[0]); execvp(cmd->argv[0], cmd->argv); err_syserr("failed to execute %s: ", cmd->argv[0]); } else if (pid < 0) err_syserr("fork failed: "); return pid; } /* Helper function that forks pipes */ static void fork_pipes(int n, struct command *cmd) { int i; int in = 0; int fd[2]; for (i = 0; i < n - 1; ++i) { pipe(fd); spawn_proc(in, fd[1], cmd + i); close(fd[1]); in = fd[0]; } if (dup2(in, 0) < 0) err_syserr("dup2() failed on stdin for %s: ", cmd[i].argv[0]); fprintf(stderr, "%d: executing %s\n", (int)getpid(), cmd[i].argv[0]); execvp(cmd[i].argv[0], cmd[i].argv); err_syserr("failed to execute %s: ", cmd[i].argv[0]); } int main(int argc, char **argv) { int i; if (argc == 1) /* There were no arguments */ { char *printenv[] = { "printenv", 0}; char *sort[] = { "sort", 0 }; char *less[] = { "less", 0 }; struct command cmd[] = { {printenv}, {sort}, {less} }; fork_pipes(3, cmd); } else { if (strcmp(argv[1], "cd") == 0) /* change directory */ { printf("change directory to %s\n", argv[2]); chdir(argv[2]); } else if (strcmp(argv[1], "exit") == 0) { printf("exit\n"); exit(0); } else { char *tmp; int len = 1; for (i = 1; i < argc; i++) { len += strlen(argv[i]) + 2; } tmp = (char *) malloc(len); tmp[0] = '\0'; int pos = 0; for (i = 1; i < argc; i++) { pos += sprintf(tmp + pos, "%s%s", (i == 1 ? "" : "|"), argv[i]); } char *printenv[] = { "printenv", 0}; char *grep[] = { "grep", "-E", tmp, NULL}; char *sort[] = { "sort", 0 }; char *less[] = { "less", 0 }; struct command cmd[] = { {printenv}, {grep}, {sort}, {less} }; fork_pipes(4, cmd); free(tmp); } } return(0); } 

示例运行

样本1:

 $ ./ft13 | cat 1733: executing less 1735: executing printenv 1736: executing sort Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.sl7NmyZPgI/Render BASH_ENV=/Users/jleffler/.bashrc CDPATH=:/Users/jleffler:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work:/Users/jleffler/ids CLICOLOR=1 …lots of environment omitted… VISUAL=vim XPC_FLAGS=0x0 XPC_SERVICE_NAME=0 _=./ft13 __CF_USER_TEXT_ENCODING=0x1F7:0x0:0x0 $ 

样本2:

 $ ./ft13 PATH | cat 1739: executing printenv 1737: executing less 1740: executing grep 1741: executing sort CDPATH=:/Users/jleffler:/Users/jleffler/src:/Users/jleffler/src/perl:/Users/jleffler/src/sqltools:/Users/jleffler/lib:/Users/jleffler/doc:/Users/jleffler/work:/Users/jleffler/ids DYLD_LIBRARY_PATH=/usr/lib:/usr/informix/11.70.FC6/lib:/usr/informix/11.70.FC6/lib/esql:/usr/informix/11.70.FC6/lib/cli GOPATH=/Users/jleffler/Software/go-1.2 LD_LIBRARY_PATH=/usr/lib:/usr/gnu/lib:/usr/gcc/v4.9.1/lib MANPATH=/Users/jleffler/man:/Users/jleffler/share/man:/usr/local/mysql/man:/usr/gcc/v4.9.1/share/man:/Users/jleffler/perl/v5.20.1/man:/usr/local/man:/usr/local/share/man:/opt/local/man:/opt/local/share/man:/usr/share/man:/usr/gnu/man:/usr/gnu/share/man PATH=/Users/jleffler/bin:/usr/informix/11.70.FC6/bin:.:/usr/local/mysql/bin:/usr/gcc/v4.9.1/bin:/Users/jleffler/perl/v5.20.1/bin:/usr/local/go/bin:/Users/jleffler/Software/go-1.2/bin:/usr/local/bin:/opt/local/bin:/usr/bin:/bin:/usr/gnu/bin:/usr/sbin:/sbin $