是否可以读取和写入popen返回的文件描述符。我有一个我想通过C控制的互动过程。如果用popen这是不可能的,有什么方法吗?
答案 0 :(得分:25)
正如已经回答的那样,popen在一个方向上工作。如果需要读写,可以使用pipe()创建管道,通过fork()和exec函数跨越一个新进程,然后使用dup2()重定向其输入和输出。无论如何,我更喜欢exec而不是popen,因为它可以让你更好地控制这个过程(例如你知道它的pid)
编辑:
如评论所示,管道只能在一个方向上使用。因此,您必须创建单独的管道进行读写。由于之前发布的示例是错误的,我删除了它并创建了一个新的,正确的:
#include<unistd.h>
#include<sys/wait.h>
#include<sys/prctl.h>
#include<signal.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>
int main(int argc, char** argv)
{
pid_t pid = 0;
int inpipefd[2];
int outpipefd[2];
char buf[256];
char msg[256];
int status;
pipe(inpipefd);
pipe(outpipefd);
pid = fork();
if (pid == 0)
{
// Child
dup2(outpipefd[0], STDIN_FILENO);
dup2(inpipefd[1], STDOUT_FILENO);
dup2(inpipefd[1], STDERR_FILENO);
//ask kernel to deliver SIGTERM in case the parent dies
prctl(PR_SET_PDEATHSIG, SIGTERM);
//replace tee with your process
execl("/usr/bin/tee", "tee", (char*) NULL);
// Nothing below this line should be executed by child process. If so,
// it means that the execl function wasn't successfull, so lets exit:
exit(1);
}
// The code below will be executed only by parent. You can write and read
// from the child using pipefd descriptors, and you can send signals to
// the process using its pid by kill() function. If the child process will
// exit unexpectedly, the parent process will obtain SIGCHLD signal that
// can be handled (e.g. you can respawn the child process).
//close unused pipe ends
close(outpipefd[0]);
close(inpipefd[1]);
// Now, you can write to outpipefd[1] and read from inpipefd[0] :
while(1)
{
printf("Enter message to send\n");
scanf("%s", msg);
if(strcmp(msg, "exit") == 0) break;
write(outpipefd[1], msg, strlen(msg));
read(inpipefd[0], buf, 256);
printf("Received answer: %s\n", buf);
}
kill(pid, SIGKILL); //send SIGKILL signal to the child process
waitpid(pid, &status, 0);
}
答案 1 :(得分:5)
popen()
和朋友不提供双向通信的原因是由于子进程中的缓冲,它将容易死锁。答案中讨论的所有临时管道和socketpair()
解决方案都存在同样的问题。
在UNIX下,大多数命令都不可信任读取一行并立即处理并打印它,除非它们的标准输出是tty。原因是默认情况下stdio缓冲用户空间中的输出,并推迟write()
系统调用,直到缓冲区已满或stdio流关闭(通常因为程序或脚本在看到EOF后即将退出)输入)。如果你通过管道写入这样一个程序的stdin,现在等待该程序的stdout的答案(不关闭入口管道),答案就会停留在stdio缓冲区中,将永远不会出现 - 这是一个僵局。
你可以通过使用伪tty与他们交谈来欺骗一些面向行的程序(例如grep
)进行不缓冲;看看libexpect(3)
。但是在一般情况下,您必须为每条消息重新运行不同的子进程,允许使用EOF来指示每条消息的结束并导致命令(或命令管道)中的任何缓冲区被刷新。显然,在表现方面不是一件好事。
在perlipc手册页中查看有关此问题的更多信息(它适用于Perl中的双向管道,但无论主程序使用何种语言,缓冲注意事项都适用。)
答案 2 :(得分:4)
你想要一些通常被称为popen2的东西。这是一个没有错误检查的basic implementation。
答案 3 :(得分:3)
答案 4 :(得分:1)
使用forkpty
(它是非标准的,但API非常好,如果你没有它,你总是可以自己实现)和exec
你想要的程序与子进程沟通。
或者,如果tty语义不符合您的喜好,您可以编写类似forkpty
的内容,但使用两个管道,每个通信方向一个,或使用socketpair
与外部程序通信在unix socket上。
答案 5 :(得分:1)
在netresolve后端之一中,我正在与脚本交谈,因此我需要写入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;
}
读取。以下函数执行一个命令,其中stdin和stdout重定向到管道。您可以使用它并根据自己的喜好进行调整。
length
https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46
(我在Can popen() make bidirectional pipes like pipe() + fork()?中使用了相同的代码)
答案 6 :(得分:0)
您不能使用popen
来使用双向管道。
事实上,有些操作系统不支持双向管道,在这种情况下,套接字对(socketpair
)是唯一的方法。
答案 7 :(得分:-1)
popen()
管道..
使用popen返回的文件描述符读取和编写子进程stdin
和stdout
(命令,&#34; w&#34;)
似乎工作正常..
我认为在我知道更好之前它会起作用,而且确实如此。 根据上面的帖子,这不应该工作..这让我有点担心。
gcc on raspbian(raspbery pi debian)