posix_spawnp并将子输出传递给字符串

时间:2012-12-15 14:32:30

标签: c++ c posix ipc

我正在努力创建流程并将子流程的输出传递给父流程的字符串。我让它在Windows上工作(使用CreatePipe和CreateProcess和ReadFile),但似乎无法在Unix上获得完全模拟的工作。这是我的代码:

#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

int main()
{
  int exit_code;
  int cout_pipe[2];
  int cerr_pipe[2];
  posix_spawn_file_actions_t action;

  if(pipe(cout_pipe) || pipe(cerr_pipe))
    cout << "pipe returned an error.\n";

  posix_spawn_file_actions_init(&action);
  posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[0]);
  posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
  posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2);

  posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
  posix_spawn_file_actions_addclose(&action, cerr_pipe[1]);

  vector<string> argmem = {"bla"};
  vector<char*> args = {&argmem[0][0], nullptr}; // I don't want to call new.

  pid_t pid;
  if(posix_spawnp(&pid, "echo", &action, NULL, &args[0], NULL) != 0)
    cout << "posix_spawnp failed with error: " << strerror(errno) << "\n";
  //close(cout_pipe[0]);
  //close(cerr_pipe[0]);

  close(cout_pipe[1]);
  close(cerr_pipe[1]);

  waitpid(pid,&exit_code,0);
  cout << "exit code: " << exit_code << "\n";

  // Read from pipes
  const size_t buffer_size = 1024;
  string buffer;
  buffer.resize(buffer_size);
  ssize_t bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  while ((bytes_read = read(cout_pipe[0], &buffer[0], buffer_size)) > 0)
  {
    cout << "read " << bytes_read << " bytes from stdout.\n";
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n";
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  }
  if(bytes_read == -1)
    cout << "Failure reading from stdout pipe.\n";
  while ((bytes_read = read(cerr_pipe[0], &buffer[0], buffer_size)) > 0)
  {
    cout << "read " << bytes_read << " bytes from stderr.\n";
    cout << buffer.substr(0, static_cast<size_t>(bytes_read)+1) << "\n";
    bytes_read = read(cout_pipe[0], &buffer[0], buffer_size);
  }
  if(bytes_read == -1)
    cout << "Failure reading from stderr pipe.\n";

  posix_spawn_file_actions_destroy(&action);
}

输出结果为:

  

退出代码:0

所以我认为一切正常,除了实际的管道。这有什么不对?我也想知道是否有一种方法可以在waitpid循环中读取管道字节,但是当我尝试这种方法时,父进程无限地挂起。

1 个答案:

答案 0 :(得分:18)

posix_spawn非常有趣且有用,这使得这个问题值得暗示 - 即使它与OP不再相关。

代码中有一些重要的错误已发布。我怀疑其中一些是绝望的黑客攻击的结果,但我不知道哪个是最初的错误:

  1. args数组不包含代表可执行文件名的argv[0]。这导致echo程序永远不会看到预期的argv[1](“bla”)。
  2. 以不合理的方式从不同的地方调用read()函数。执行此操作的正确方法是仅将read作为while循环的控件表达式的一部分进行调用。
  3. 在从管道读取之前调用
  4. waitpid()。这可以防止I / O完成(至少在非平凡的情况下)。
  5. 此代码的一个更微妙的问题是,在从stdout读取任何内容之前,尝试读取所有孩子的stderr。原则上,这可能会导致孩子在尝试写入stderr时阻止,从而阻止程序完成。为此创建有效的解决方案更加复杂,因为它要求您可以从具有可用数据的管道中读取。我使用了poll()。另一种方法是使用多个线程。
  6. 此外,我使用sh(命令shell,即bash)作为子进程。这提供了大量额外的灵活性,例如运行管道而不是单个可执行文件。但是,特别是,使用sh提供了简单方便,无需管理命令行的解析。

    /*BINFMTCXX: -std=c++11 -Wall -Werror
    */
    
    #include <spawn.h> // see manpages-posix-dev
    #include <poll.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    int main()
    {
      int exit_code;
      int cout_pipe[2];
      int cerr_pipe[2];
      posix_spawn_file_actions_t action;
    
      if(pipe(cout_pipe) || pipe(cerr_pipe))
        cout << "pipe returned an error.\n";
    
      posix_spawn_file_actions_init(&action);
      posix_spawn_file_actions_addclose(&action, cout_pipe[0]);
      posix_spawn_file_actions_addclose(&action, cerr_pipe[0]);
      posix_spawn_file_actions_adddup2(&action, cout_pipe[1], 1);
      posix_spawn_file_actions_adddup2(&action, cerr_pipe[1], 2);
    
      posix_spawn_file_actions_addclose(&action, cout_pipe[1]);
      posix_spawn_file_actions_addclose(&action, cerr_pipe[1]);
    
    //string command = "echo bla"; // example #1
      string command = "pgmcrater -width 64 -height 9 |pgmtopbm |pnmtoplainpnm";
      string argsmem[] = {"sh","-c"}; // allows non-const access to literals
      char * args[] = {&argsmem[0][0],&argsmem[1][0],&command[0],nullptr};
    
      pid_t pid;
      if(posix_spawnp(&pid, args[0], &action, NULL, &args[0], NULL) != 0)
        cout << "posix_spawnp failed with error: " << strerror(errno) << "\n";
    
      close(cout_pipe[1]), close(cerr_pipe[1]); // close child-side of pipes
    
      // Read from pipes
      string buffer(1024,' ');
      std::vector<pollfd> plist = { {cout_pipe[0],POLLIN}, {cerr_pipe[0],POLLIN} };
      for ( int rval; (rval=poll(&plist[0],plist.size(),/*timeout*/-1))>0; ) {
        if ( plist[0].revents&POLLIN) {
          int bytes_read = read(cout_pipe[0], &buffer[0], buffer.length());
          cout << "read " << bytes_read << " bytes from stdout.\n";
          cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n";
        }
        else if ( plist[1].revents&POLLIN ) {
          int bytes_read = read(cerr_pipe[0], &buffer[0], buffer.length());
          cout << "read " << bytes_read << " bytes from stderr.\n";
          cout << buffer.substr(0, static_cast<size_t>(bytes_read)) << "\n";
        }
        else break; // nothing left to read
      }
    
      waitpid(pid,&exit_code,0);
      cout << "exit code: " << exit_code << "\n";
    
      posix_spawn_file_actions_destroy(&action);
    }