首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >popen同时读写

popen同时读写
EN

Stack Overflow用户
提问于 2011-05-30 09:27:53
回答 8查看 27.7K关注 0票数 35

是否可以读取和写入popen返回的文件描述符。我有一个交互的过程,我想通过C来控制。如果用popen做不到这一点,有什么办法可以绕过它吗?

EN

回答 8

Stack Overflow用户

发布于 2011-05-30 13:28:24

正如已经回答的那样,popen只在一个方向上工作。如果需要读写,可以使用管道()创建管道,通过fork()和exec函数跨越新进程,然后使用dup2()重定向其输入和输出。无论如何,我更喜欢exec而不是popen,因为它可以让你更好地控制进程(例如,你知道它的pid)

编辑:

正如注释所建议的,管道只能在一个方向上使用。因此,您必须为读取和写入创建单独的管道。因为之前发布的示例是错误的,所以我删除了它,并创建了一个新的正确的示例:

代码语言:javascript
复制
#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);
}
票数 38
EN

Stack Overflow用户

发布于 2011-05-30 10:21:42

您想要一个通常称为popen2的东西。这是一个没有错误检查的basic implementation (通过网络搜索找到的,而不是我的代码):

代码语言:javascript
复制
// http://media.unpythonic.net/emergent-files/01108826729/popen2.c

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

#include "popen2.h"

int popen2(const char *cmdline, struct popen2 *childinfo) {
    pid_t p;
    int pipe_stdin[2], pipe_stdout[2];

    if(pipe(pipe_stdin)) return -1;
    if(pipe(pipe_stdout)) return -1;

    //printf("pipe_stdin[0] = %d, pipe_stdin[1] = %d\n", pipe_stdin[0], pipe_stdin[1]);
    //printf("pipe_stdout[0] = %d, pipe_stdout[1] = %d\n", pipe_stdout[0], pipe_stdout[1]);

    p = fork();
    if(p < 0) return p; /* Fork failed */
    if(p == 0) { /* child */
        close(pipe_stdin[1]);
        dup2(pipe_stdin[0], 0);
        close(pipe_stdout[0]);
        dup2(pipe_stdout[1], 1);
        execl("/bin/sh", "sh", "-c", cmdline, NULL);
        perror("execl"); exit(99);
    }
    childinfo->child_pid = p;
    childinfo->to_child = pipe_stdin[1];
    childinfo->from_child = pipe_stdout[0];
    close(pipe_stdin[0]);
    close(pipe_stdout[1]);
    return 0; 
}

//#define TESTING
#ifdef TESTING
int main(void) {
    char buf[1000];
    struct popen2 kid;
    popen2("tr a-z A-Z", &kid);
    write(kid.to_child, "testing\n", 8);
    close(kid.to_child);
    memset(buf, 0, 1000);
    read(kid.from_child, buf, 1000);
    printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 
    printf("from child: %s", buf); 
    printf("waitpid() -> %d\n", waitpid(kid.child_pid, NULL, 0));
    printf("kill(%d, 0) -> %d\n", kid.child_pid, kill(kid.child_pid, 0)); 
    return 0;
}
#endif
票数 6
EN

Stack Overflow用户

发布于 2012-04-25 21:38:33

popen()和朋友不提供双向通信的原因是,由于子进程中的缓冲,它很容易死锁。答案中讨论的所有临时管道和socketpair()解决方案都存在相同的问题。

在UNIX下,不能相信大多数命令读取一行并立即处理并打印它,除非它们的标准输出是tty。原因是默认情况下,stdio在用户空间中缓冲输出,并延迟write()系统调用,直到缓冲区已满或stdio流关闭(通常是因为程序或脚本在看到EOF on input后即将退出)。如果您通过管道写入此类程序的stdin,然后等待来自该程序的stdout的应答(而不关闭入口管道),则该应答将卡在stdio缓冲区中,并且永远不会出现--这是一个死锁。

您可以通过使用伪tty与一些面向行的程序(例如grep)对话来欺骗它们不进行缓冲;看看libexpect(3)。但在一般情况下,您必须为每条消息重新运行不同的子进程,从而允许使用EOF来表示每条消息的结尾,并导致刷新命令(或命令管道)中的任何缓冲区。显然,在性能方面不是一件好事。

perlipc手册页中可以看到关于这个问题的更多信息(它适用于Perl语言中的双向管道,但是缓冲方面的考虑无论主程序使用哪种语言都适用)。

票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/6171552

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档