Linux:使用管道标准输入/标准输出执行子进程

新手上路,请多包涵

使用 Linux 和 C++,我想要一个执行以下操作的函数:

 string f(string s)
{
    string r = system("foo < s");
    return r;
}

显然上述方法不起作用,但你明白了。我有一个字符串 s 我想作为应用程序“foo”的子进程执行的标准输入传递,然后我想将其标准输出记录到字符串 r 然后归还。

我应该使用哪种 Linux 系统调用或 POSIX 函数组合?我使用的是 Linux 3.0,不需要该解决方案来处理旧系统。

原文由 Andrew Tomazos 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 936
2 个回答

由 erpini 提供的代码不能像所写的那样工作。请注意,例如,在父级中关闭的管道末端将在以后使用。看着

close(wpipefd[1]);

以及随后对该封闭描述符的写入。这只是换位,但它表明这个代码从未被使用过。下面是我测试过的版本。不幸的是,我改变了代码风格,所以这没有被接受为对 erpini 代码的编辑。

唯一的结构变化是我只重定向子路径中的 I/O(注意 dup2 调用仅在子路径中。)这非常重要,因为否则父级的 I/O 会搞砸。感谢 erpini 最初的答案,我在开发这个答案时使用了它。

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

#define PIPE_READ 0
#define PIPE_WRITE 1

int createChild(const char* szCommand, char* const aArguments[], char* const aEnvironment[], const char* szMessage) {
  int aStdinPipe[2];
  int aStdoutPipe[2];
  int nChild;
  char nChar;
  int nResult;

  if (pipe(aStdinPipe) < 0) {
    perror("allocating pipe for child input redirect");
    return -1;
  }
  if (pipe(aStdoutPipe) < 0) {
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    perror("allocating pipe for child output redirect");
    return -1;
  }

  nChild = fork();
  if (0 == nChild) {
    // child continues here

    // redirect stdin
    if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1) {
      exit(errno);
    }

    // redirect stdout
    if (dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1) {
      exit(errno);
    }

    // redirect stderr
    if (dup2(aStdoutPipe[PIPE_WRITE], STDERR_FILENO) == -1) {
      exit(errno);
    }

    // all these are for use by parent only
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]);

    // run child process image
    // replace this with any exec* function find easier to use ("man exec")
    nResult = execve(szCommand, aArguments, aEnvironment);

    // if we get here at all, an error occurred, but we are in the child
    // process, so just exit
    exit(nResult);
  } else if (nChild > 0) {
    // parent continues here

    // close unused file descriptors, these are for child only
    close(aStdinPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]);

    // Include error check here
    if (NULL != szMessage) {
      write(aStdinPipe[PIPE_WRITE], szMessage, strlen(szMessage));
    }

    // Just a char by char read here, you can change it accordingly
    while (read(aStdoutPipe[PIPE_READ], &nChar, 1) == 1) {
      write(STDOUT_FILENO, &nChar, 1);
    }

    // done with these in this example program, you would normally keep these
    // open of course as long as you want to talk to the child
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
  } else {
    // failed to create child
    close(aStdinPipe[PIPE_READ]);
    close(aStdinPipe[PIPE_WRITE]);
    close(aStdoutPipe[PIPE_READ]);
    close(aStdoutPipe[PIPE_WRITE]);
  }
  return nChild;
}

原文由 Ammo Goettsch 发布,翻译遵循 CC BY-SA 3.0 许可协议

由于您希望双向访问该进程,因此您必须使用管道显式地执行 popen 在幕后所做的事情。我不确定这是否会在 C++ 中发生任何变化,但这是一个纯 C 示例:

 void piped(char *str){
    int wpipefd[2];
    int rpipefd[2];
    int defout, defin;
    defout = dup(stdout);
    defin = dup (stdin);
    if(pipe(wpipefd) < 0){
            perror("Pipe");
            exit(EXIT_FAILURE);
    }
    if(pipe(rpipefd) < 0){
            perror("Pipe");
            exit(EXIT_FAILURE);
    }
    if(dup2(wpipefd[0], 0) == -1){
            perror("dup2");
            exit(EXIT_FAILURE);
    }
    if(dup2(rpipefd[1], 1) == -1){
            perror("dup2");
            exit(EXIT_FAILURE);
    }
    if(fork() == 0){
            close(defout);
            close(defin);
            close(wpipefd[0]);
            close(wpipefd[1]);
            close(rpipefd[0]);
            close(rpipefd[1]);
            //Call exec here. Use the exec* family of functions according to your need
    }
    else{
            if(dup2(defin, 0) == -1){
                    perror("dup2");
                    exit(EXIT_FAILURE);
            }
            if(dup2(defout, 1) == -1){
                    perror("dup2");
                    exit(EXIT_FAILURE);
            }
            close(defout);
            close(defin);
            close(wpipefd[1]);
            close(rpipefd[0]);
            //Include error check here
            write(wpipefd[1], str, strlen(str));
            //Just a char by char read here, you can change it accordingly
            while(read(rpipefd[0], &ch, 1) != -1){
                    write(stdout, &ch, 1);
            }
    }

}

实际上你这样做:

  1. 创建管道并将 stdout 和 stdin 重定向到两个管道的末端(请注意,在 linux 中, pipe() 创建单向管道,因此您需要使用两个管道来实现您的目的)。
  2. Exec 现在将启动一个新进程,该进程具有标准输入和标准输出的管道末端。
  3. 关闭未使用的描述符,将字符串写入管道,然后开始读取进程可能转储到另一个管道的任何内容。

dup() 用于在文件描述符表中创建重复条目。而 dup2() 更改描述符指向的内容。

注意:正如 Ammo@ 在他的解决方案中提到的,我上面提供的或多或少是一个模板,如果您只是尝试执行代码,它将不会运行,因为显然缺少 exec*(函数系列),所以child 将在 fork() 之后几乎立即终止。

原文由 eerpini 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题