c - 通过 C 中的管道重定向标准输入和标准输出适用于外部程序,但不适用于递归调用
问题描述
我正在尝试通过 C 中的 stdin 和 stdout 的管道重定向与分叉的子进程通信。我已经设法让它适用ls
于在子进程中执行的 shell 命令(例如)。但是,我无法递归执行相同的程序并通过管道从子进程到父进程(在此测试中对父进程)重定向输出(由printf()
、fprintf()
to 、... 打印),尽管这可行适用于或类似的命令。stdout
stdout
ls
以下是我尝试解决此问题的方法:
- 我创建了一个管道,读取端是父进程,子进程应该写入写入端。
- 进程分叉,两个进程分别关闭未使用的端。
- 管道的写入端被重定向
STDOUT_FILENO
并关闭 - 子进程递归执行程序(称为./to2)
如前所述,如果我ls
在子进程中执行,这确实有效,但如果我尝试递归调用同一个程序,则不会。这是我试图让它工作的测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
static void usage(void){
fprintf(stderr,"RIP");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]){
if(argc > 1){
dprintf(STDOUT_FILENO,"Please work\n");
printf("\n THIS IS A MESSAGE FROM THE CHILD \n");
fputs("Pretty Please!\n",stdout);
fflush(stdout);
exit(EXIT_SUCCESS);
}
int p1[2];
if(-1 == pipe(p1)) {
fprintf(stderr,"pipe\n");
fprintf(stderr,"%s\n",strerror(errno));
usage();
}
int f = fork();
if(f == 0){
close(p1[0]);
if(dup2(p1[1],STDOUT_FILENO) < 0){
fprintf(stderr,"dup2\n");
usage();
}
close(p1[1]);
//I want this to work:
//execlp("./to2", "./to2", "-e");
//This works fine:
execlp("ls", "ls");
exit(EXIT_SUCCESS);
} else if (f == -1) {
usage();
} else {
close(p1[1]);
int w = -1;
if(-1 == wait(&w)) usage();
char b[12];
memset(b,0,12);
read(p1[0],&b,12);
char reading_buf[1];
while(read(p1[0], reading_buf, 1) > 0){
write(1, reading_buf, STDOUT_FILENO);
}
close(p1[0]);
}
}
出于测试目的,使用附加参数递归调用该函数,而调用父程序时不使用附加参数(因此是if(argc>1)
)。在最终的程序中,通过其他方式避免了无限递归。
我理解错了吗?我很困惑,唯一似乎不起作用的是重定向我自己 程序的输出......
非常感谢您,非常感谢您的任何帮助或想法。
解决方案
主要问题正是评论中概述的 - 你没有execlp()
正确调用(也不ls
是替代方法)。您必须将这些函数调用的最后一个参数设置为显式空指针,如以下代码所示,这是问题中内容的大部分温和编辑版本:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
static void usage(void)
{
fprintf(stderr, "RIP\n");
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
if (argc > 1)
{
dprintf(STDOUT_FILENO, "Please work\n");
printf("THIS IS A MESSAGE FROM THE CHILD\n");
fputs("Pretty Please!\n", stdout);
fflush(stdout);
exit(EXIT_SUCCESS);
}
int p1[2];
if (-1 == pipe(p1))
{
fprintf(stderr, "pipe: %s\n", strerror(errno));
usage();
}
int f = fork();
if (f == 0)
{
close(p1[0]);
if (dup2(p1[1], STDOUT_FILENO) < 0)
{
fprintf(stderr, "dup2: %s\n", strerror(errno));
usage();
}
close(p1[1]);
execlp(argv[0], argv[0], "-e", (char *)0);
fprintf(stderr, "failed to exec %s again\n", argv[0]);
exit(EXIT_FAILURE);
}
else if (f == -1)
{
usage();
}
else
{
close(p1[1]);
char b[13];
memset(b, 0, 13);
if (read(p1[0], &b, 12) < 0)
{
fprintf(stderr, "Failed to read from pipe (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
int len = strcspn(b, "\n");
printf("M1 [%.*s]\n", len, b);
char reading_buf[1];
while (read(p1[0], reading_buf, 1) > 0)
{
write(1, reading_buf, STDOUT_FILENO);
}
close(p1[0]);
int w = -1;
if (-1 == wait(&w))
usage();
}
return 0;
}
应该强调两个重要的变化:
- 这段代码回显了第一行数据——由它写入的数据
dprintf()
——而原始代码只是读取它并丢弃它。 wait()
调用是在输入之后,而不是之前。如果子进程要写入的数据多于一组固定消息,它可能会阻塞等待父进程读取一些数据,而父进程会阻塞等待子进程退出。这将是一个僵局。
该usage()
函数没有正确命名——它没有报告如何运行程序。如果子进程失败,我也会以失败状态退出,而不是成功execlp()
。
在特殊情况下,wait()
调用可能会报告某个子节点的退出状态,而不是被分叉的子节点。通常最好使用循环来收获这样的孩子。然而,所需的情况是极其特殊的——使用exec*()
函数启动父进程的进程必须先前创建了一些它没有等待的子进程,以便它们被父进程继承(因为 PID 不会改变一个exec*()
电话)。
推荐阅读
- android - 其他方的视频未在 Twilio Video 中以 react-native 显示。Web和android之间的调用
- android - 无法创建 ViewModel 类的实例
- java - 声音生成应用程序问题 - 无法为 > 6000 Hz 的声音频率生成声音
- php - 如何在 php 中使用 foreach 循环检索 api 数据
- javascript - 谷歌地图信息窗口 ReactJS
- python - 使用 python 将 pandas 数据框写入 SQL
- elasticsearch - ElasticSeach 从 Apache Hadoop 读取数据
- python - Pandas ValueError:Series 的真值不明确。在计算前一个元素和当前元素之间的最大值时
- java - 从 hashmap 中删除所有符合特定条件的对象
- android - 阻止内容溢出和自动调整大小以扩展文本