使用 fork() 和 exec() 在 C 中执行命令行管道
Executing a command-line pipeline in C with fork() and exec()
我询问了我的代码,答案是它不正确。
现在我想知道如何调整和改进,让我不再有这些错误?
execvp does not return, except if an error occurs, so if all works,
the enclosing function will never return.
the value 'i' is already past the end of the array in 'cmd' due to the
prior loop, so 'cmd[i].argv[0] is not correct.
cmd is not an array, of struct command, so should not be indexed
the first entry in cmd.argv is a pointer to an array where the last
entry is NULL. the execvp will work on that (and only that) array so
all other pointers to arrays will be ignored
There are a large number of errors in the code. for instance, the
first time through the loop in fork_pipe() 'in' contains garbage. the
second parameter passed to execvp() needs to be a pointer to character
strings, with a final NULL pointer. That final NULL pointer is
missing, There are plenty more problems
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
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) < 0) {
perror("execvp failed");
exit(1);
} else {
return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);
}
}
int main (int argc, char ** argv) {
int i;
if (argc == 1) { /* There were no arguments */
const char *printenv[] = { "printenv", 0};
const char *sort[] = { "sort", 0 };
const char *less[] = { "less", 0 };
struct command cmd [] = { {printenv}, {sort}, {less} };
return fork_pipes (3, cmd);
}
if (argc > 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] = '[=10=]';
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()
上进行测试(如果它 returns,它失败了),但你确实需要在它之后进行错误报告和退出调用。
“cmd
不是数组”的注释似乎是假的; fork_pipes()
里面是一个数组。在spawn_proc()
.
中不作为数组使用
我觉得评论'The first entry in cmd.argv
is a pointer to an array where the last entry is NULL. The execvp
will work on that (and only that) array so all other pointers to arrays will be ignored'也是假的。我认为他们忽略了您正在创建一个结构命令数组。
我认为注释'the value i
is already past the end of the array in cmd
due to the prior loop, so cmd[i].argv[0]
is not correct'是不正确的,因为循环是for (i = 0; i < n - 1; i++)
所以i
在循环之后是n-1
,数组cmd
有元素 0..n-1
要寻址。
但是第一次调用spawn_proc()
时in
的值确实是垃圾。也许你可以简单地将它设置为 0
(STDIN_FILENO
) 并且没问题,但你需要验证这一点。但是关于 execvp()
的第二个参数的评论很奇怪——强制转换应该不存在,但除此之外代码对我来说看起来还不错。我应该补充一点,我还没有 运行 编译器来处理这些,所以到目前为止我所说的任何内容都将由编译器进行更正。但我也不是随便做分析的……你是不是用你的编译器编译集挑剔:gcc -Wall -Wextra -Werror
作为最低限度(我使用更多选项!)?
在fork_pipes()
中,execvp()
的if
测试和else
子句很奇怪。您只需要调用 execvp()
、perror()
和 exit()
.
以上评论基本准确。这是一些修改后的代码,但修改主要是装饰性的。 err_syserr()
函数是基于我用来报告错误的。它是一个可变参数函数,也报告系统错误。它比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
。
其他更改只是装饰性的,而不是实质性的(未初始化的 in
是唯一需要修复的严重错误)。我使用了我的错误报告代码,并检查了一些额外的系统调用(例如 dup2()
系统调用),并清理了 execvp()
和错误报告。我将 exit
和 cd
的测试移到了通用代码之前,以避免重复测试。此外,您使用的是 strncmp()
,而 exit
的测试仅查看 ex
,但 ex
是系统命令……使用 strcmp()
。我在条件中使用 strcmp(x, y) == 0
;使用中的关系运算符模仿我正在测试的关系运算(因此 strcmp(x, y) >= 0
测试 x
大于或等于 y
,等等)。
现代 POSIX 不需要 #include <sys/types.h>
作为包含。其他 headers 根据需要包含它。
来源:ft13.c
编译为 ft13
。
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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] = '[=11=]';
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);
}
例子运行s
示例 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
$
我询问了我的代码,答案是它不正确。
execvp does not return, except if an error occurs, so if all works, the enclosing function will never return.
the value 'i' is already past the end of the array in 'cmd' due to the prior loop, so 'cmd[i].argv[0] is not correct.
cmd is not an array, of struct command, so should not be indexed
the first entry in cmd.argv is a pointer to an array where the last entry is NULL. the execvp will work on that (and only that) array so all other pointers to arrays will be ignored
There are a large number of errors in the code. for instance, the first time through the loop in fork_pipe() 'in' contains garbage. the second parameter passed to execvp() needs to be a pointer to character strings, with a final NULL pointer. That final NULL pointer is missing, There are plenty more problems
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
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) < 0) {
perror("execvp failed");
exit(1);
} else {
return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);
}
}
int main (int argc, char ** argv) {
int i;
if (argc == 1) { /* There were no arguments */
const char *printenv[] = { "printenv", 0};
const char *sort[] = { "sort", 0 };
const char *less[] = { "less", 0 };
struct command cmd [] = { {printenv}, {sort}, {less} };
return fork_pipes (3, cmd);
}
if (argc > 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] = '[=10=]';
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()
上进行测试(如果它 returns,它失败了),但你确实需要在它之后进行错误报告和退出调用。“
cmd
不是数组”的注释似乎是假的;fork_pipes()
里面是一个数组。在spawn_proc()
. 中不作为数组使用
我觉得评论'The first entry in
cmd.argv
is a pointer to an array where the last entry is NULL. Theexecvp
will work on that (and only that) array so all other pointers to arrays will be ignored'也是假的。我认为他们忽略了您正在创建一个结构命令数组。我认为注释'the value
i
is already past the end of the array incmd
due to the prior loop, socmd[i].argv[0]
is not correct'是不正确的,因为循环是for (i = 0; i < n - 1; i++)
所以i
在循环之后是n-1
,数组cmd
有元素0..n-1
要寻址。但是第一次调用
spawn_proc()
时in
的值确实是垃圾。也许你可以简单地将它设置为0
(STDIN_FILENO
) 并且没问题,但你需要验证这一点。但是关于execvp()
的第二个参数的评论很奇怪——强制转换应该不存在,但除此之外代码对我来说看起来还不错。我应该补充一点,我还没有 运行 编译器来处理这些,所以到目前为止我所说的任何内容都将由编译器进行更正。但我也不是随便做分析的……你是不是用你的编译器编译集挑剔:gcc -Wall -Wextra -Werror
作为最低限度(我使用更多选项!)?在
fork_pipes()
中,execvp()
的if
测试和else
子句很奇怪。您只需要调用execvp()
、perror()
和exit()
.
以上评论基本准确。这是一些修改后的代码,但修改主要是装饰性的。 err_syserr()
函数是基于我用来报告错误的。它是一个可变参数函数,也报告系统错误。它比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
。
其他更改只是装饰性的,而不是实质性的(未初始化的 in
是唯一需要修复的严重错误)。我使用了我的错误报告代码,并检查了一些额外的系统调用(例如 dup2()
系统调用),并清理了 execvp()
和错误报告。我将 exit
和 cd
的测试移到了通用代码之前,以避免重复测试。此外,您使用的是 strncmp()
,而 exit
的测试仅查看 ex
,但 ex
是系统命令……使用 strcmp()
。我在条件中使用 strcmp(x, y) == 0
;使用中的关系运算符模仿我正在测试的关系运算(因此 strcmp(x, y) >= 0
测试 x
大于或等于 y
,等等)。
现代 POSIX 不需要 #include <sys/types.h>
作为包含。其他 headers 根据需要包含它。
来源:ft13.c
编译为 ft13
。
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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] = '[=11=]';
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);
}
例子运行s
示例 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
$