我在C ++中使用模拟文件系统实现管道(主要是C)。它需要在主机shell中运行命令,但在模拟文件系统上执行管道本身。
我可以通过pipe()
,fork()
和system()
系统调用实现此目的,但我更愿意使用popen()
(处理创建管道,分叉)一个进程,并将命令传递给shell)。这可能是不可能的,因为(我认为)我需要能够从管道的父进程写入,在子进程结束时读取,从子进程写回输出,最后从父进程读取该输出。我系统上popen()
的手册页说明了双向管道,但是我的代码需要在一个只支持单向管道的旧版本的系统上运行。
通过上面的单独调用,我可以打开/关闭管道来实现这一目标。这可能是popen()
吗?
对于一个简单的例子,要运行ls -l | grep .txt | grep cmds
,我需要:
ls -l
;阅读其输出ls -l
的输出传回我的模拟器grep .txt
的管道输出上的主机上运行ls -l
grep cmds
的管道输出上的主机上运行grep .txt
man popen
从Mac OS X:
popen()
功能'打开'a 通过创建双向进程 管道,分叉和调用shell。 之前popen()
打开的所有流 父进程中的调用已关闭 在新的子进程中。 从历史上看,popen()
已经实施 带有单向管;因此, 仅有popen()
的许多实现 允许mode参数指定 阅读或写作,而不是两者。因为popen()
现在使用a实现 双向管道,模式参数 可以请求双向数据流。 mode参数是指向a的指针 以null结尾的字符串,必须是 'r'用于阅读,'w'用于写作,或者 'r +'用于阅读和写作。
答案 0 :(得分:19)
我建议您编写自己的功能来为您进行管道/分叉/系统管理。您可以让函数生成一个进程并返回读/写文件描述符,如...
typedef void pfunc_t (int rfd, int wfd);
pid_t pcreate(int fds[2], pfunc_t pfunc) {
/* Spawn a process from pfunc, returning it's pid. The fds array passed will
* be filled with two descriptors: fds[0] will read from the child process,
* and fds[1] will write to it.
* Similarly, the child process will receive a reading/writing fd set (in
* that same order) as arguments.
*/
pid_t pid;
int pipes[4];
/* Warning: I'm not handling possible errors in pipe/fork */
pipe(&pipes[0]); /* Parent read/child write pipe */
pipe(&pipes[2]); /* Child read/parent write pipe */
if ((pid = fork()) > 0) {
/* Parent process */
fds[0] = pipes[0];
fds[1] = pipes[3];
close(pipes[1]);
close(pipes[2]);
return pid;
} else {
close(pipes[0]);
close(pipes[3]);
pfunc(pipes[2], pipes[1]);
exit(0);
}
return -1; /* ? */
}
您可以在其中添加所需的任何功能。
答案 1 :(得分:11)
您好像回答了自己的问题。如果您的代码需要在不支持popen
打开双向管道的旧系统上工作,那么您将无法使用popen
(至少不能使用提供的那个)。
真正的问题是关于旧系统的确切功能。特别是,他们的pipe
是否支持创建双向管道?如果他们有pipe
可以创建双向管道,但popen
没有,那么我会编写代码的主流以使用带有双向管道的popen
,并提供popen
的实现,它可以使用双向管道,在需要的地方编译。
如果您需要支持的系统足够pipe
仅支持单向管道,那么您几乎不得不使用pipe
,fork
,dup2
等。,你自己。我可能仍然将它包装在一个像<{1}}的现代版本一样几乎的函数中,但不是返回一个文件句柄,而是用两个文件句柄填充一个小结构,一个用于孩子的popen
,另一个用于孩子的stdin
。
答案 2 :(得分:8)
POSIX规定popen()
电话不是为了提供双向通信而设计的:
popen()的mode参数是一个指定I / O模式的字符串:
- 如果mode是r,当子进程启动时,其文件描述符STDOUT_FILENO应该是管道的可写端,而文件描述符fileno(stream)在调用进程中,其中stream是popen返回的流指针(),应为管道的可读端。
- 如果mode是w,当子进程启动时,其文件描述符STDIN_FILENO应该是管道的可读端,而文件描述符fileno(stream)在调用进程中,其中stream是popen返回的流指针( ),应为管道的可写端。
- 如果mode是任何其他值,则结果未指定。
醇>
任何便携式代码都不会做出任何假设。 BSD popen()
与您的问题描述类似。
此外,管道与套接字不同,每个管道文件描述符都是单向的。您必须创建两个管道,一个为每个方向配置。
答案 3 :(得分:1)
无需在每个进程中创建两个管道并浪费文件描述符。只需使用套接字即可。 https://stackoverflow.com/a/25177958/894520
答案 4 :(得分:1)
在netresolve后端之一中,我正在与脚本交谈,因此我需要写入stdin
并从其stdout
读取。以下函数执行一个命令,其中stdin和stdout重定向到管道。您可以使用它并根据自己的喜好进行调整。
static bool
start_subprocess(char *const command[], int *pid, int *infd, int *outfd)
{
int p1[2], p2[2];
if (!pid || !infd || !outfd)
return false;
if (pipe(p1) == -1)
goto err_pipe1;
if (pipe(p2) == -1)
goto err_pipe2;
if ((*pid = fork()) == -1)
goto err_fork;
if (*pid) {
/* Parent process. */
*infd = p1[1];
*outfd = p2[0];
close(p1[0]);
close(p2[1]);
return true;
} else {
/* Child process. */
dup2(p1[0], 0);
dup2(p2[1], 1);
close(p1[0]);
close(p1[1]);
close(p2[0]);
close(p2[1]);
execvp(*command, command);
/* Error occured. */
fprintf(stderr, "error running %s: %s", *command, strerror(errno));
abort();
}
err_fork:
close(p2[1]);
close(p2[0]);
err_pipe2:
close(p1[1]);
close(p1[0]);
err_pipe1:
return false;
}
https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46
(我在popen simultaneous read and write中使用了相同的代码)
答案 5 :(得分:1)
以下是代码(C ++,但可以轻松转换为C):
#include <unistd.h>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <utility>
// Like popen(), but returns two FILE*: child's stdin and stdout, respectively.
std::pair<FILE *, FILE *> popen2(const char *__command)
{
// pipes[0]: parent writes, child reads (child's stdin)
// pipes[1]: child writes, parent reads (child's stdout)
int pipes[2][2];
pipe(pipes[0]);
pipe(pipes[1]);
if (fork() > 0)
{
// parent
close(pipes[0][0]);
close(pipes[1][1]);
return {fdopen(pipes[0][1], "w"), fdopen(pipes[1][0], "r")};
}
else
{
// child
close(pipes[0][1]);
close(pipes[1][0]);
dup2(pipes[0][0], STDIN_FILENO);
dup2(pipes[1][1], STDOUT_FILENO);
execl("/bin/sh", "/bin/sh", "-c", __command, NULL);
exit(1);
}
}
用法:
int main()
{
auto [p_stdin, p_stdout] = popen2("cat -n");
if (p_stdin == NULL || p_stdout == NULL)
{
printf("popen2() failed\n");
return 1;
}
const char msg[] = "Hello there!";
char buf[32];
printf("I say \"%s\"\n", msg);
fwrite(msg, 1, sizeof(msg), p_stdin);
fclose(p_stdin);
fread(buf, 1, sizeof(buf), p_stdout);
fclose(p_stdout);
printf("child says \"%s\"\n", buf);
return 0;
}
可能的输出:
I say "Hello there!"
child says " 1 Hello there!"