web-dev-qa-db-ja.com

Linux 3.0:パイプされたstdin / stdoutを使用して子プロセスを実行する

Linux 3.0/C++の場合:

次のような関数が欲しいのですが。

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

明らかに上記は機能しませんが、あなたはその考えを理解します。アプリケーション「foo」の子プロセス実行の標準入力として渡したい文字列sがあり、その標準出力を文字列rに記録して返したいと思います。

Linuxのsyscallまたはposix関数のどの組み合わせを使用する必要がありますか?

14
Andrew Tomazos

Eerpiniが提供するコードは、記述どおりに機能しません。たとえば、親で閉じられているパイプの端が後で使用されることに注意してください。見る

close(wpipefd[1]); 

その後、その閉じた記述子に書き込みます。これは単なる転置ですが、このコードが使用されたことがないことを示しています。以下は私がテストしたバージョンです。残念ながら、コードスタイルを変更したため、これはeerpiniのコードの編集として受け入れられませんでした。

唯一の構造上の変更は、子のI/Oのみをリダイレクトすることです(dup2呼び出しは子パスにのみあることに注意してください)。そうしないと、親のI/Oが台無しになるため、これは非常に重要です。私がこれを開発する際に使用した最初の答えをくれた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;
}
43
Ammo Goettsch

プロセスへの双方向アクセスが必要なため、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を2つのパイプの端にリダイレクトします(Linuxでは、pipe()が単方向パイプを作成するため、目的に応じて2つのパイプを使用する必要があることに注意してください)。
  2. Execは、stdinとstdoutのパイプの端を持つ新しいプロセスを開始します。
  3. 未使用の記述子を閉じ、文字列をパイプに書き込んでから、プロセスが他のパイプにダンプする可能性のあるものをすべて読み取り始めます。

dup()は、ファイル記述子テーブルに重複エントリを作成するために使用されます。 dup2()は、記述子が指すものを変更します。

注:Ammo @が彼のソリューションで述べたように、上記で提供したのは多かれ少なかれテンプレートです。明らかにexec *(関数のファミリー)が欠落しているため、コードを実行しようとすると実行されません。子はfork()のほぼ直後に終了します。

2
eerpini

Ammoのコードには、いくつかのエラー処理のバグがあります。子プロセスは、重複失敗後に終了するのではなく戻ってきます。おそらく、子の重複は次のように置き換えることができます。

    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]);
1
jws