C++ popen 同时读写

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6171552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 19:37:20  来源:igfitidea点击:

popen simultaneous read and write

c++c

提问by Lee

Is it possible to read and write to a file descriptor returned by popen. I have an interactive process I'd like to control through C. If this isn't possible with popen, is there any way around it?

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

回答by Patryk

As already answered, popen works in one direction. If you need to read and write, You can create a pipe with pipe(), span a new process by fork() and exec functions and then redirect its input and outputs with dup2(). Anyway I prefer exec over popen, as it gives you better control over the process (e.g. you know its pid)

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

EDITED:

编辑:

As comments suggested, a pipe can be used in one direction only. Therefore you have to create separate pipes for reading and writing. Since the example posted before was wrong, I deleted it and created a new, correct one:

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

#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);
}

回答by DomQ

The reason popen()and friends don't offer bidirectional communication is that it would be deadlock-prone, due to buffering in the subprocess. All the makeshift pipework and socketpair()solutions discussed in the answers suffer from the same problem.

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

Under UNIX, most commands cannot be trusted to read one line and immediately process it and print it, except if their standard output is a tty. The reason is that stdio buffers output in userspace by default, and defers the write()system call until either the buffer is full or the stdio stream is closed (typically because the program or script is about to exit after having seen EOF on input). If you write to such a program's stdin through a pipe, and now wait for an answer from that program's stdout (without closing the ingress pipe), the answer is stuck in the stdio buffers and will never come out- This is a deadlock.

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

You can trick some line-oriented programs (eg grep) into not buffering by using a pseudo-tty to talk to them; take a look at libexpect(3). But in the general case, you would have to re-run a different subprocess for each message, allowing to use EOF to signal the end of each message and cause whatever buffers in the command (or pipeline of commands) to be flushed. Obviously not a good thing performance-wise.

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

See more info about this problem in the perlipcman page (it's for bi-directional pipes in Perl but the buffering considerations apply regardless of the language used for the main program).

perlipc手册页中查看有关此问题的更多信息(它适用于 Perl 中的双向管道,但无论主程序使用哪种语言,缓冲注意事项都适用)。

回答by Mr Fooz

You want something often called popen2. Here's a basic implementationwithout error checking (found by a web search, not my code):

你想要一些经常被称为 popen2 的东西。这是一个没有错误检查的基本实现(通过网络搜索找到,而不是我的代码):

// 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

回答by rid

popen()can only open the pipe in read or write mode, not both. Take a look at thisthread for a workaround.

popen()只能以读或写模式打开管道,不能同时打开。看看这个线程的解决方法。

回答by R.. GitHub STOP HELPING ICE

Use forkpty(it's non-standard, but the API is very nice, and you can always drop in your own implementation if you don't have it) and execthe program you want to communicate with in the child process.

使用forkpty(它是非标准的,但 API 非常好,如果你没有它,你可以随时删除你自己的实现)和exec你想在子进程中与之通信的程序。

Alternatively, if tty semantics aren't to your liking, you could write something like forkptybut using two pipes, one for each direction of communication, or using socketpairto communicate with the external program over a unix socket.

或者,如果 tty 语义不符合您的喜好,您可以编写类似forkpty但使用两个管道的东西,每个管道一个用于通信方向,或者socketpair用于通过 unix 套接字与外部程序通信。

回答by Pavel ?imerda

In one of netresolvebackends I'm talking to a script and therefore I need to write to its stdinand read from its stdout. The following function executes a command with stdin and stdout redirected to a pipe. You can use it and adapt it to your liking.

在其中一个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

https://github.com/crossdistro/netresolve/blob/master/backends/exec.c#L46

(I used the same code in Can popen() make bidirectional pipes like pipe() + fork()?)

(我在Can popen() 中使用了相同的代码来制作像 pipe() + fork() 这样的双向管道?

回答by Chris Jester-Young

You can't use popento use two-way pipes.

您不能使用popen双向管道。

In fact, some OSs don't support two-way pipes, in which case a socket-pair (socketpair) is the only way to do it.

事实上,一些操作系统不支持双向管道,在这种情况下,套接字对 ( socketpair) 是唯一的方法。

回答by TCTEC ptyltd

popen works for me in both directions (read and write) I have been using a popen()pipe in both directions..

popen 在两个方向(读和写)都对我有用 我一直popen()在两个方向使用管道..

Reading and writing a child process stdinand stdoutwith the file descriptor returned by popen(command,"w")

读取和写入子进程stdinstdout使用 popen(command,"w") 返回的文件描述符

It seems to work fine..

它似乎工作正常..

I assumed it would work before I knew better, and it does. According posts above this shouldn't work.. which worries me a little bit.

我以为它会在我知道更好之前起作用,并且确实如此。根据上面的帖子,这应该行不通……这让我有点担心。

gcc on raspbian (raspbery pi debian)

gcc on raspbian (raspbery pi debian)