简体   繁体   English

Linux 3.0:使用管道stdin / stdout执行子进程

[英]Linux 3.0: Executing child process with piped stdin/stdout

Under Linux 3.0 / C++: 在Linux 3.0 / C ++下:

I would like a function that does the following: 我想要一个执行以下操作的函数:

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

Obviously the above doesn't work, but you get the idea. 显然上面的方法不起作用,但你明白了。 I have a string s that I would like to pass as the standard input of a child process execution of application "foo", and then I would like to record its standard output to string r and then return it. 我有一个字符串s,我想传递作为应用程序“foo”的子进程执行的标准输入,然后我想将其标准输出记录到字符串r然后返回它。

What combination of linux syscalls or posix functions should I use? 我应该使用linux系统调用或posix函数的组合?

The code provided by eerpini does not work as written. eerpini提供的代码无法正常工作。 Note, for example, that the pipe ends that are closed in the parent are used afterwards. 请注意,例如,之后使用在父级中关闭的管道末端。 Look at 看着

close(wpipefd[1]); 

and the subsequent write to that closed descriptor. 以及随后写入该封闭描述符。 This is just transposition, but it shows this code has never been used. 这只是换位,但它表明此代码从未被使用过。 Below is a version that I have tested. 以下是我测试过的版本。 Unfortunately, I changed the code style, so this was not accepted as an edit of eerpini's code. 不幸的是,我改变了代码风格,所以这不被接受为eerpini代码的编辑。

The only structural change is that I only redirect the I/O in the child (note the dup2 calls are only in the child path.) This is very important, because otherwise the parent's I/O gets messed up. 唯一的结构变化是我只重定向子进程中的I / O(注意dup2调用仅在子路径中。)这非常重要,因为否则父进程的I / O会搞乱。 Thanks to eerpini for the initial answer, which I used in developing this one. 感谢eerpini的初步答案,我用它来开发这个。

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

Since you want bidirectional access to the process, you would have to do what popen does behind the scenes explicitly with pipes. 由于您希望对流程进行双向访问,因此您必须使用管道明确地执行popen在幕后的操作。 I am not sure if any of this will change in C++, but here is a pure C example : 我不确定这是否会在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);
            }
    }

}

Effectively you do this : 有效地你这样做:

  1. Create pipes and redirect the stdout and stdin to the ends of the two pipes (note that in linux, pipe() creates unidirectional pipes, so you need to use two pipes for your purpose). 创建管道并将stdout和stdin重定向到两个管道的末端(请注意,在linux中,pipe()会创建单向管道,因此您需要使用两个管道来实现)。
  2. Exec will now start a new process which has the ends of the pipes for stdin and stdout. Exec现在将启动一个新进程,其中包含stdin和stdout管道的末尾。
  3. Close the unused descriptors, write the string to the pipe and then start reading whatever the process might dump to the other pipe. 关闭未使用的描述符,将字符串写入管道,然后开始读取进程可能转储到其他管道的任何内容。

dup() is used to create a duplicate entry in the file descriptor table. dup()用于在文件描述符表中创建重复条目。 While dup2() changes what the descriptor points to. 而dup2()更改描述符指向的内容。

Note : As mentioned by Ammo@ in his solution, what I provided above is more or less a template, it will not run if you just tried to execute the code since clearly there is a exec* (family of functions) missing, so the child will terminate almost immediately after the fork(). 注意:正如Ammo @在他的解决方案中所提到的,我上面提供的内容或多或少是一个模板,如果你只是试图执行代码它将无法运行,因为很明显有一个exec *(函数族)缺失,所以child会在fork()之后几乎立即终止。

Ammo's code has some error handling bugs. Ammo的代码有一些错误处理错误。 The child process is returning after dup failure instead of exiting. 子进程在dup失败后返回而不是退出。 Perhaps the child dups can be replaced with: 也许孩子的副本可以替换为:

    if (dup2(aStdinPipe[PIPE_READ], STDIN_FILENO) == -1 ||
        dup2(aStdoutPipe[PIPE_WRITE], STDOUT_FILENO) == -1 ||
        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]);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM