如何解决使用叉子和管道来模仿 linux 管道命令
我的目标是模仿 linux 管道命令,例如 ls |种类。而不是简单地排序。但不是打字 |用户输入:例如 "./program ls : sort" == "ls | sort"
我需要使用 fork() 和 pipe() 来完成这个任务。我有一个 MRE 设置,它一次只允许我运行一个命令,但我不知道如何将它设置为第二个命令的 stdout 为 stdin。每当我尝试在父级中 dup() close() and exec()
时似乎出现问题?我有一个设置,解析用户给出的输入,我获取参数 A 的 argA,其中包含诸如 ls 或 sort 之类的命令,以及参数 A 参数的 ArgAP,以防用户想要指定 -lh 或 -r 等。同样的事情对于 argB。
我目前将此程序设置为硬编码以执行 bc 命令,但可以通过分配一些参数轻松修复。请帮助我,因为我一直坚持这个!
//################ #-for include
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>
#include <dirent.h>
//################
int main(int b,char ** locations) {
int ok = 0;
int dots = 0;
char argA[1000];
char argB[1000];
char argAP[1000];
char argBP[1000];
while (locations[ok] != NULL) {
//printf("%s \n",locations[ok]);
if (strcmp(locations[ok],":") == 0) {
dots = 1;
}
ok++;
} //printf("%s %d \n",locations[2],b);
strcpy(argA,"");
strcpy(argB,"");
strcpy(argAP,"");
strcpy(argBP,"");
if (dots == 0) {
int x = 1;
strcat(argA,locations[x]);
strcat(argA," ");
x++;
while (locations[x] != NULL) {
strcat(argAP,locations[x]);
strcat(argAP," ");
x++;
}
printf("%s%s \n",argA,argAP);
}
if (dots == 1) {
int x = 1;
int compare = strcmp(locations[x],":");
if (strcmp(locations[1],":") == 0) {
printf("one arg\n");
strcat(argA,locations[x]);
strcat(argA," ");
x++;
while (locations[x] != NULL) {
strcat(argAP,locations[x]);
strcat(argAP," ");
x++;
}
printf("%s%s \n",argAP);
} else {
printf("two args\n");
strcat(argA," ");
compare = strcmp(locations[x],":");
x++;
compare = strcmp(locations[x],":");
while (compare != 0) {
printf("%d \n",x);
strcat(argAP," ");
compare = strcmp(locations[x],":");
x++;
}
printf("argA: %s%s \n",argAP);
x++;
strcat(argB,locations[x]);
strcat(argB," ");
x++;
while (locations[x] != NULL) {
strcat(argBP,locations[x]);
strcat(argBP," ");
x++;
}
printf("argB: %s%s \n",argB,argBP);
}
}
// fork/piping
int i,n;
int fd[2];
pipe(fd);
int rd = fd[0]; // rd points to 0 (read) in pipe
int wt = fd[1]; // wt points to 1 (write) in pipe
if (fork()) {
close(rd);
write(wt,"2*1*9*1",strlen("2*1*9*1"));
write(wt,"\n",1);
close(wt);
exit(0);
} else {
close(wt);
close(0); // close zero
dup(rd); // dup rd into lowest possible orbit
close(rd);
execlp("bc","bc",NULL); // reading from zero means reading from rd!
exit(1);
}
return 0;
}
我已经注释掉了需要帮助的部分。我如何使用一个命令的管道将结果通过 fork 输入第二个命令?我认为这几乎是不可能的,因为通过我的尝试,我只能通过我在此处编写的此设置获得一个命令。
解决方法
您应该始终尝试将问题拆分为可以先解决和验证的较小子问题。
例如,请考虑是否为您提供了以下 run.h:
#ifndef RUN_H
#define RUN_H
#include <sys/types.h>
/** Create a close-on-exec pipe with descriptors not standard streams
*
* Both read and write end of the pipe will be close-on-exec,* and neither of them will be 0 (stdin),1 (stdout),or 2 (stderr).
*
* @fd Read ([0]) and write ([1]) ends of the pipe
* @return 0 if success,-1 with errno set if error
*/
int safe_pipe(int fd[2]);
/* pgid: */
enum {
NEW_SESSION = -2,NO_CHANGE = -1,NEW_PGROUP = 0,};
/* Descriptors: */
enum {
DEV_NULL = -1,STANDARD_INPUT = 0,STANDARD_OUTPUT = 1,STANDARD_ERROR = 2,};
/** Execute a binary in a child process
*
* This function uses a control pipe to check if the specified binary could be executed
* (started; not completed!),and to provide the errno number if not.
* It is careful to ensure even oddball descriptor configurations work.
* It is up to the parent to close any pipe descriptors specified after the call.
*
* @pathname Name or path to the binary to be executed.
* @args NULL-terminated array of command-line arguments.
* Note that args[0] is the command name itself.
* @pgid Process group to use: NEW_SESSION,NO_CHANGE,NEW_PGROUP,* or a process group ID. NO_CHANGE runs the child process
* in the same session and process group as the current process.
* @infd Standard input descriptor. DEV_NULL,or a pipe descriptor.
* @outfd Standard output descriptor. DEV_NULL,1,or a pipe descriptor.
* @errfd Standard error descriptor. DEV_NULL,2,or a pipe descriptor.
* @return PID of the child process,or -1 with errno set if an error occurs.
*/
pid_t run(const char *pathname,const char *args[],pid_t pgid,int infd,int outfd,int errfd);
/** Wait for all child processes to finish,and reap them
*
* errno is always set when this function returns:
* ECHILD if there are no more child processes
* EINTR if wait was interrupted by signal delivery
* or any other positive value returned by reaped().
*
* @reaped NULL,or a function called for each reaped child process.
* First parameter is the PID of the child process that exited,* the second parameter is the "packed" exit status code; see man 2 wait.
* If reaped() returns a negative value,wait_all_children() will
* immediately return with that value,keeping the same errno value.
* If reaped() returns a positive value,wait_all_children() will
* immediately return with the reaped process count,and that value in errno.
* @return Number of processes reaped,or negative if an error occurs.
*/
int wait_all_children(int (*reaped)(pid_t,int));
#endif /* RUN_H */
及其知识共享零许可(随意使用!)实施,run.c:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "run.h"
/* Create a close-on-exec pipe whose read and write ends do not
* overlap with standard descriptors (stdin,stdout,stderr).
* Returns 0 if success,-1 with errno set if error.
*/
int safe_pipe(int fd[2])
{
unsigned int close_mask = 0;
int err = 0;
int pfd[2];
fd[0] = -1;
fd[1] = -1;
if (pipe2(pfd,O_CLOEXEC) == -1) {
/* errno set by pipe2() */
return -1;
}
do {
/* Make sure read end does not shadow standard descriptors */
while (pfd[0] >= 0 && pfd[0] <= 2) {
close_mask |= 1 << pfd[0];
pfd[0] = fcntl(pfd[0],F_DUPFD_CLOEXEC,3);
if (pfd[0] == -1) {
err = errno;
break;
}
}
if (err)
break;
/* Make sure write end does not shadow standard descriptors */
while (pfd[1] >= 0 && pfd[1] <= 2) {
close_mask |= 1 << pfd[1];
pfd[1] = fcntl(pfd[1],3);
if (pfd[1] == -1) {
err = errno;
break;
}
}
if (err)
break;
/* Close any temporarily used descriptors. */
if (close_mask & (1<<0))
close(0);
if (close_mask & (1<<1))
close(1);
if (close_mask & (1<<2))
close(2);
/* Success! */
fd[0] = pfd[0];
fd[1] = pfd[1];
return 0;
} while (0);
/* Failed. Close all related descriptors. */
if (pfd[0] != -1)
close(pfd[0]);
if (pfd[1] != -1)
close(pfd[1]);
if (close_mask & (1<<0))
close(0);
if (close_mask & (1<<1))
close(1);
if (close_mask & (1<<2))
close(2);
errno = err;
return -1;
}
/* Open the null device to a specific descriptor.
*/
static int dev_null(int to_descriptor)
{
int fd;
if (to_descriptor == -1) {
errno = EBADF;
return -1;
}
close(to_descriptor);
fd = open("/dev/null",O_RDWR | O_NOCTTY);
if (fd == -1)
return -1; /* errno set by open() */
if (fd == to_descriptor)
return 0; /* We got lucky! */
if (dup2(fd,to_descriptor) == -1) {
const int saved_errno = errno;
close(fd);
errno = saved_errno;
return -1;
}
close(fd);
return 0;
}
/* Execute a binary with the specified command-line arguments
* (args[] array terminated by a NULL pointer),* and redirecting standard input,output,and error to the specified descriptors
* (which may,and should be,close-on-exec),or -1 to redirect to /dev/null.
* pgid can be -2 for a new session,-1 for no change,0 for new process group,or >0 for a specific process group.
* Returns the PID of the new child process,or -1 with errno set if error.
*/
pid_t run(const char *pathname,int errfd)
{
if (!pathname || !*pathname || !args || !args[0] || !args[0][0]) {
/* NULL or empty pathname (path or name) to the executable. */
errno = EINVAL;
return -1;
}
/* Create the control pipe we use between parent and child to monitor exec() success/failure. */
int ctrlfd[2];
if (safe_pipe(ctrlfd) == -1) {
/* errno set by safe_pipe(). */
return -1;
}
pid_t child = fork();
if (child == -1) {
/* Cannot fork a child process. */
const int saved_errno = errno;
close(ctrlfd[0]);
close(ctrlfd[1]);
errno = saved_errno;
return -1;
} else
if (!child) {
/* Child process. */
unsigned int close_mask = 0;
int err = 0;
do {
/* Close parent (read) end of the control pipe. */
close(ctrlfd[0]);
/* Adjust process group. */
if (pgid == NEW_SESSION) {
if (setsid() == -1) {
err = errno;
break;
}
} else
if (pgid == NEW_PGROUP) {
if (setpgid(0,0) == -1) {
err = errno;
break;
}
} else
if (pgid > 0) {
if (setpgid(0,pgid) == -1) {
err = errno;
break;
}
} else
if (pgid != NO_CHANGE) {
err = EINVAL;
break;
}
/* Make sure infd does not shadow standard output or standard error. */
while (infd == 1 || infd == 2) {
close_mask |= 1 << infd;
infd = fcntl(infd,3);
if (infd == -1) {
err = errno;
break;
}
}
if (err)
break;
/* Make sure outfd does not shadow standard input or standard error. */
while (outfd == 0 || outfd == 2) {
close_mask |= 1 << outfd;
outfd = fcntl(outfd,3);
if (outfd == -1) {
err = errno;
break;
}
}
if (err)
break;
/* Make sure errfd does not shadow standard input or standard output. */
while (errfd == 0 || errfd == 1) {
close_mask |= 1 << errfd;
errfd = fcntl(errfd,3);
if (errfd == -1) {
err = errno;
break;
}
}
if (err)
break;
/* Close unneeded descriptors. */
if ((close_mask & (1<<0)) || infd == -1)
close(0);
if ((close_mask & (1<<1)) || outfd == -1)
close(1);
if ((close_mask & (1<<2)) || errfd == -1)
close(2);
/* Redirect standard input. */
if (infd == DEV_NULL) {
if (dev_null(0) == -1) {
err = errno;
break;
}
infd = 0;
} else
if (infd != 0) {
if (dup2(infd,0) == -1) {
err = errno;
break;
}
close(infd);
infd = 0;
}
/* Redirect standard output. */
if (outfd == DEV_NULL) {
if (dev_null(1) == -1) {
err = errno;
break;
}
outfd = 1;
} else
if (outfd != 1) {
if (dup2(outfd,1) == -1) {
err = errno;
break;
}
close(outfd);
outfd = 1;
}
/* Redirect standard error. */
if (errfd == DEV_NULL) {
if (dev_null(2) == -1) {
err = errno;
break;
}
errfd = 2;
} else
if (errfd != 2) {
if (dup2(errfd,2) == -1) {
err = errno;
break;
}
close(errfd);
errfd = 2;
}
/* Make sure the standard descriptors are not close-on-exec. */
if (fcntl(0,F_SETFD,0) == -1 ||
fcntl(1,0) == -1 ||
fcntl(2,0) == -1) {
err = errno;
break;
}
/* Close the unneeded temporary descriptors. */
if (close_mask & (1<<0))
close(0);
if (close_mask & (1<<1))
close(1);
if (close_mask & (1<<2))
close(2);
close_mask = 0;
/* Execute. */
if (strchr(pathname,'/'))
execv(pathname,(char *const *)args); /* pathname has a slash,so it is a path to a binary */
else
execvp(pathname,(char *const *)args); /* pathname has no slash,so it specifies the binary name only */
/* Failed. */
err = errno;
} while (0);
/* Send err to parent via the control pipe. */
{
const char *ptr = (const char *)(&err);
const char *const end = (const char *)(&err) + sizeof err;
while (ptr < end) {
ssize_t n = write(ctrlfd[1],ptr,(size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1) {
/* Should never occur */
break;
} else
if (errno != EINTR) {
/* I/O error writing to the pipe too! */
break;
}
}
}
/* The kernel will close all open descriptors in the process. */
exit(127);
}
/* Parent process. */
/* Close read end of control pipe,so we detect if the child process exec() succeeded. */
close(ctrlfd[1]);
/* Read from the control pipe,to determine if child process exec succeeds or not. */
{
int err = 0;
char *ptr = (char *)(&err);
char *const end = (char *)(&err) + sizeof err;
while (ptr < end) {
ssize_t n = read(ctrlfd[0],(size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n == 0) {
break;
} else
if (n != -1) {
err = EIO;
ptr = end;
break;
} else
if (errno != EINTR) {
err = errno;
ptr = end;
break;
}
}
close(ctrlfd[0]);
/* Treat partially received errors and received zero as EIO. */
if (ptr > (char *)(&err)) {
if (ptr != end || !err)
err = EIO;
}
if (err) {
/* Child failed to exec; reap it. */
/* Reap child process. Ignore errors,and retry if interrupted. */
pid_t p;
do {
p = waitpid(child,NULL,0);
} while (p == -1 && errno == EINTR);
errno = err;
return -1;
}
}
/* Success. */
errno = 0;
return child;
}
int wait_all_children(int (*reaped)(pid_t,int))
{
int count = 0;
while (1) {
int status = 0;
pid_t pid;
pid = wait(&status);
if (pid == -1) {
/* errno set by wait() */
return count;
} else
if (pid < 1) {
/* C library or Linux kernel bug! */
errno = EIO;
return count;
}
count++;
if (reaped) {
int retval = reaped(pid,status);
if (retval < 0) {
/* errno set by reaped() */
return retval;
} else
if (retval > 0) {
errno = retval;
return count;
}
}
}
}
和 Makefile 以从 example.c 构建示例程序:
CC := gcc
CFLAGS := -Wall -Wextra -O2
LDFLAGS :=
TARGETS := example
.PHONY: all clean
all: $(TARGETS)
clean:
rm -f $(TARGETS) *.o
%.o: %.c
$(CC) $(CFLAGS) -c $^
example: example.o run.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
请注意,本论坛将Tabs 转换为空格,并且Makefile 缩进必须使用Tabs 而不是空格。幸运的是,通过将初始空格转换为制表符,运行 sed -e 's|^ *|\t|' -i Makefile
修复了上述问题以及大多数其他 Makefile。
上面的想法是为了帮助您开始正确的道路:
-
safe_pipe()
创建一个管道,当您执行任何 exec() 调用时,该管道的两端都会自动关闭。即使您关闭了一些标准描述符(标准输入、输出或错误),它也可以确保管道末端不会干扰标准描述符。 -
wait_all_children()
等待当前进程的所有直接子进程(即该进程已分叉的进程),直到没有更多子进程,等待被信号传递或可选报告功能中断(它作为参数)返回非零值。 -
run()
派生一个子进程,根据需要重定向标准描述符,甚至处理进程组。它使用控制管道检测exec()错误,不等待子进程退出,只等待子进程启动。进程组很有用,因为如果每个逻辑任务都在自己的进程组中,您可以使用否定的进程组 ID 向特定组中的每个进程发送信号(如 KILL 或 TERM)。这确保了对于复杂的任务,当整个任务需要被杀死时,无论它已经分叉了多少个进程,您都可以很好地清理它。不过,对于简单的命令,我们不必担心进程组。
在我们做一个简单的示例程序之后,检查上述内容会更好,比如运行ls -laF | tr a-z A-Z | cat
,它列出了当前目录中的所有文件,但是将小写的 a 到 z 转换为大写,使用三个子进程:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "run.h"
int report(pid_t pid,int code)
{
if (WIFEXITED(code)) {
if (WEXITSTATUS(code))
printf("Process %d exited with status %d.\n",(int)pid,WEXITSTATUS(code));
else
printf("Process %d exited with success (status 0).\n",(int)pid);
} else
if (WIFSIGNALED(code)) {
printf("Process %d died from signal %d.\n",WTERMSIG(code));
} else {
printf("Process %d died from unknown causes.\n",(int)pid);
}
return 0;
}
int main(void)
{
const char *cmd_a[] = { "ls","-laF",NULL };
const char *cmd_b[] = { "tr","a-z","A-Z",NULL };
const char *cmd_c[] = { "cat",NULL };
int a_to_b[2],b_to_c[2];
pid_t a,b,c;
if (safe_pipe(a_to_b) == -1 || safe_pipe(b_to_c) == -1) {
fprintf(stderr,"Cannot create pipes: %s.\n",strerror(errno));
return EXIT_FAILURE;
}
c = run(cmd_c[0],cmd_c,b_to_c[0],STANDARD_OUTPUT,STANDARD_ERROR);
if (c == -1) {
fprintf(stderr,"%s: %s.\n",cmd_c[0],strerror(errno));
return EXIT_FAILURE;
}
b = run(cmd_b[0],cmd_b,a_to_b[0],b_to_c[1],STANDARD_ERROR);
if (b == -1) {
fprintf(stderr,cmd_b[0],strerror(errno));
kill(c,SIGKILL);
wait_all_children(NULL);
return EXIT_FAILURE;
}
a = run(cmd_a[0],cmd_a,DEV_NULL,a_to_b[1],STANDARD_ERROR);
if (a == -1) {
fprintf(stderr,cmd_a[0],strerror(errno));
kill(b,SIGKILL);
kill(c,SIGKILL);
wait_all_children(NULL);
return EXIT_FAILURE;
}
close(a_to_b[0]);
close(a_to_b[1]);
close(b_to_c[0]);
close(b_to_c[1]);
wait_all_children(report);
return EXIT_SUCCESS;
}
report()
函数只是报告哪个子进程退出以及如何退出。您可以省略它,使用 wait_all_children(NULL);
而不是 wait_all_children(report);
,但报告函数对于调试和检查您可以在不同情况下观察到的退出状态非常有用。
我们必须使用管道:a_to_b
和 b_to_c
。该对的第一个描述符(a_to_b[0]
和 b_to_c[0]
)始终是读端,第二个(a_to_b[1]
和 b_to_c[1]
)是写端。我们的三个子进程是a
、b
和c
,所以我们希望a
的标准输出是a_to_b[1]
、b
' s 标准输入为 a_to_b[0]
,b
的标准输出为 b_to_c[1]
,c
的标准输入为 b_to_c[1]
。
我们需要先创建管道。
然后,我们需要运行(fork 和 exec)子进程。因为子进程会阻塞直到他们得到输入(或者他们看到输入结束),所以我们在管道中从最后到第一个创建子进程:首先是“消费者”,然后是“生产者”。这样,如果我们在整个链都没有问题之前失败,生产者应该仍在等待输入。
如果我们无法创建一些子进程,我们确实需要杀死已经启动的进程,最好等待它们退出(通常称为,收割它们)。
当子进程启动后,父进程需要关闭它的管道描述符副本,这样当“生产者”(管道列表中的第一个进程)退出并关闭管道的写端时,读取结束报告输入结束。
如果父进程不关闭管道(写结束)描述符的副本,“消费者”(从管道读取的子进程)将永远不会检测到输入结束——因为理论上,parent 进程仍然可以写入管道! – 一切都会像“挂起”一样。
此时,管道中的进程开始工作。此时父进程可以做其他事情。 (一个有趣的场景是当人们希望使用过滤器:一系列进程,通常是一个脚本,将数据从一种格式转换为另一种格式。过滤器输出管道的读取端很有可能(或管道链)和输入管道的写端要父级可以访问,这样父级在写端写入要转换的数据,从读端读取转换后的数据。有点棘手,因为我们不能假设我们可以在读取任何东西之前写出所有东西,所以通常使用带有 select()
或 poll()
的非阻塞 I/O。没有什么困难 本身,只需要正确完成即可。)
由于父进程除了等待管道进程完成它们的工作外,无事可做,因此父进程只是等待它们退出。
因为有Makefile,所以只需要运行make all && ./example
即可编译运行示例程序。
因为整个问题被拆分为子问题(以对标准流造成最少问题的方式创建管道,并且不会意外泄漏到错误的子进程;并且分叉并启动子进程重定向其标准流,以稳健可靠的方式),示例程序简短且在概念层面易于理解。
在玩过那个例子之后,是时候探索和解释 run.c 如何实现这些功能,以及为什么做出它做出的选择,但我已经被否决了因没有回答上述问题而给负分,所以我将就此打住。尽管如此,我仍然相信以这种方式一步一步地解决问题,在经过测试和理解的部分之上构建解决方案,而不是试图“修复”OP的当前代码,是正确的“答案”。 (再说一次,这正是我只以访客身份发帖,从不注册的原因。)
,原件
一旦要替换子进程的stdin
,就需要使用dup2()
函数。
以下手册部分解释了为什么 dup()
函数永远无法满足您的目的:
dup() 系统调用创建文件描述符 oldfd 的副本, 使用编号最小的未使用文件描述符作为新的 描述符。
以下手册部分解释了为什么 dup2()
函数可以解决您的问题:
dup2() 系统调用执行与 dup() 相同的任务,但是 使用最小编号的未使用文件描述符,它使用文件 newfd 中指定的描述符编号。
要解决您的问题,请将 dup(rd)
调用替换为 dup2(rd,STDIN_FILENO)
。您也可以删除 close(0)
调用,一旦 dup2()
函数关闭 newfd
(如果它已经在使用)。
如果文件描述符 newfd 之前打开过,它会被静默 重用前关闭。
编辑 #1
我之前写的并没有解决问题,一旦 close(0); dup(rd);
将与 dup2(rd,0)
具有相同的效果,如下面提到的 this user。所以,我按原样编译了你的代码,运行后,我
得到了这个结果:
$ gcc -std=c99 -o program program.c
$ ./program ls : sort
two args
argA: ls
argB: sort
18
$
如您所见,最后一行显示了 18
,即 2*1*9*1
的结果。
现在,请注意父进程在写入描述为 wt
的文件后立即退出 - 在子进程中执行的 stdin
命令的新 bc
。这意味着父进程可能会在子进程完成之前退出。我强烈建议您在父进程退出之前使用 wait()
或 waitpid()
调用测试您的代码。例如:
// (...)
if (fork()) {
close(rd);
write(wt,"2*1*9*1",strlen("2*1*9*1"));
write(wt,"\n",1);
close(wt);
wait(NULL);
exit(0);
} else {
close(wt);
close(0); // close zero
dup(rd); // dup rd into lowest possible orbit
close(rd);
execlp("bc","bc",NULL);
exit(1);
}
我还用 execlp("bc",NULL);
行替换了 execlp("bc",NULL);
行。我删除的零相当于 NULL
,表示使用 execlp()
执行的命令的参数列表的末尾。
编辑 #2(实现)
阅读整个代码,我们可以将您的实现分为两部分:
- 解析程序的参数以适应
execlp()
函数的语法; - 分叉进程以执行第二个命令,并将第一个命令的结果作为输入。
如果您阅读 exec()
函数系列的手册页,您会注意到函数 execvp()
在这个程序中更有用,因为 execvp()
函数的第二个参数与程序的参数类型相同:以 NULL
结尾的字符串数组。
按照此步骤,您可以轻松解析程序的参数以适应 execvp()
:
- 遍历程序的参数;
- 找到管道符号的位置;
- 在那个位置,放
NULL
来表示第一个命令的参数结束; - 将下一个位置的地址保存为第二个命令参数的开始。
解析完程序的参数后,是时候创建管道并 fork 进程了。在子进程中,在执行第一个命令之前,将 stdout
替换为管道的写端。在父进程中,在执行第二个命令之前,将 stdin
替换为管道的读端。
这是我编写、运行和测试的完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define PIPE_SYMBOL ":"
int main ( int argc,char **argv ) {
/* Validates the usage. At least is needed the program's name,two commands and the pipe symbol */
if ( argc < 4 ) {
fprintf(stderr,"usage: command-1 [args-1...] : command-2 [args-2...]\n");
return EXIT_FAILURE;
}
/* The start of the first comment is allways the start of the program arguments array */
char **command1 = &argv[1];
/* The start of the second command is undefined,once it depends where the pipe symbol is located */
char **command2 = NULL;
/* Finds the position of the pipe symbol */
for ( int i = 0 ; argv[i] != NULL ; i++ ) {
/* When found,... */
if ( strcmp(PIPE_SYMBOL,argv[i]) == 0 ) {
/* ... replaces it for NULL,so the first command array is NULL terminated and... */
argv[i] = NULL;
/* ... the next position is the start of the second command */
command2 = &argv[i+1];
break;
}
}
/* If the pipe symbol is missing or if there is no command after the pipe symbol,bad usage */
if ( command2 == NULL || command2[0] == NULL ) {
fprintf(stderr,"usage: command-1 [args-1...] : command-2 [args-2...]\n");
return EXIT_FAILURE;
}
pid_t pid;
int pipefd[2];
if ( pipe(pipefd) == -1 ) {
perror("creating pipe");
return EXIT_FAILURE;
}
if ( (pid = fork()) == -1 ) {
perror("creating child process");
return EXIT_FAILURE;
}
/* Child process executes the first command */
if ( pid == 0 ) {
close(pipefd[0]);
close(STDOUT_FILENO);
dup(pipefd[1]);
close(pipefd[1]);
execvp(command1[0],command1);
perror("executing first command");
return EXIT_FAILURE;
}
/* Parent process executes the second command */
close(pipefd[1]);
close(STDIN_FILENO);
dup(pipefd[0]);
close(pipefd[0]);
execvp(command2[0],command2);
perror("executing second command");
return EXIT_FAILURE;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。