C++ 启动脚本(bash、python ...)并使用 stdin/stdout 进行数据传输 [linux]

Posted

技术标签:

【中文标题】C++ 启动脚本(bash、python ...)并使用 stdin/stdout 进行数据传输 [linux]【英文标题】:C++ start a script ( bash, python ... ) and using stdin/stdout for datatransfare [linux] 【发布时间】:2016-04-17 20:21:07 【问题描述】:

我想从我的 C++ 代码中调用一个 python 脚本。 python 脚本如下所示:

    hello.py
    #!/usr/bin/python
    import sys
    print "Hello!"
    Readin = sys.stdin.read()
    print Readin

C++ 代码来自堆栈溢出的另一个问题。它应该如何工作:

创建一对管道。

使用fork() 创建子进程。

child 正在将其管道弯曲到标准输入/标准输出。关闭另一端 并启动脚本。

父亲正在监听管道read(),接收输入。而在这之后, 发送消息write()

当输入switch (readResult = read(childToPa... 时,程序不会从fathers 行返回。

我也不知道这个写作部分是否在做它的工作。这样做是一个很有前途的想法,还是有其他可行的可能性? 谢谢!

看起来像:

// maybe not all includes are necessary
#include <iostream>
#include <fstream>
#include <string>
#include <errno.h>
#include <sys/stat.h> // mkdir
#include <stdlib.h>     // system()
#include <unistd.h> // rmdir
#include <cstring> // memset

// wait:
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;

int main() 

  char target[] = "./hello.py";

  enum PIPE_FILE_DESCRIPTERS 
    READ_FD = 0, WRITE_FD = 1
  ;

  enum CONSTANTS 
    BUFFER_SIZE = 100
  ;

  int parentToChild[2];
  int childToParent[2];
  pid_t pid;
  string dataReadFromChild;
  char buffer[BUFFER_SIZE + 1];
  memset(buffer,0x00,BUFFER_SIZE + 1);
  ssize_t readResult;
  int status;

  int retPipe1 =  pipe(parentToChild);
  int retPipe2 =  pipe(childToParent);

  switch (pid = fork()) 
  case -1:
    printf("Fork failed");
    exit(-1);

  case 0: /* Child will start scripts*/ 
  
    // Bending stdin/out to the pipes?
    int retdup21 = dup2(parentToChild[READ_FD], STDIN_FILENO);
    int retdup22 = dup2(childToParent[WRITE_FD], STDOUT_FILENO);
    int retdup23 = dup2(childToParent[WRITE_FD], STDERR_FILENO);
    // Close in this Process the other sides of the pipe
    int retclose1 = close(parentToChild[WRITE_FD]);
    int retclose2 = close(childToParent[READ_FD]);

    int retexe = execlp( target ," ");              // warning not enough variable arguments to fit a sentinel [-Wformat=]

    printf("This line should never be reached!!!"); // why? what happens if execlp finishes?
    exit(-1);
    break;  // to make the compiler happy =)
  
  default: /* Parent */
    cout << "Child " << pid << " process running..." << endl;

    // close the other ends of the pipe from the other process.
    int retdup21 = close(parentToChild[READ_FD]);
    int retdup22 = close(childToParent[WRITE_FD]);

    // readtry
    while (true) 
      switch (readResult = read(childToParent[READ_FD], buffer, 1)) // execution does not return from this function.
      
      case 0: /* End-of-File, or non-blocking read. */
      
        cout << "End of file reached..." << endl << "Data received was (" << dataReadFromChild.size() << "):" << endl
            << dataReadFromChild << endl;

        cout << "starting writing" << endl;
        char bufferW[] = "\"AElement\":\"Something\"\0";


               int writeResult = write(parentToChild[WRITE_FD],bufferW,sizeof(bufferW));
               int saveerrno = errno;

               if( -1 == writeResult)
               
                 cout << "errno while writing: " << errno << std::endl;
                 if ( 9 == saveerrno )
                   cout << "Errno Bad File descriptor" << endl;
               

               cout << "Write Result: " << writeResult << std::endl;

        int retWait = waitpid(pid, &status, 0);

        cout << endl << "Child exit staus is:  " << WEXITSTATUS(status) << endl << endl;

        exit(0);
      
      case -1:
      
        if ((errno == EINTR) || (errno == EAGAIN)) 
          errno = 0;
          break;
         else 
          printf("read() failed");
          exit(-1);
        
      
      default:
        dataReadFromChild.append(buffer, readResult);
        printf("%s",buffer);
        memset(buffer,0x00,BUFFER_SIZE + 1);
        break;
      
     /* while ( true ) */
   /* switch ( pid = fork() )*/

【问题讨论】:

首先,在另一个程序中运行 Python 代码有什么关系?答:不是。因此,您可以轻松地减少您的代码和问题,更接近于一个最小的示例。然而,即便如此,我想知道你从哪里得到这种方法的想法?它不是新的,不是独特的,实际上已经很成熟了。因此,找到现有的工作示例应该是微不足道的! @Ulrich Eckhardt 该程序集有一个 c++ 核心。这是获得工作的输入。核心是准备这项工作,更新数据库等。在此之后,这项工作将由 python 完成。核心将留在未来。但是将来要做的工作类型会发生变化。所以我们寻找了一种简单的方法来做到这一点。使用 API -> C++ 准备 -> Python 执行输入脚本文件名。有没有更好的方法来执行此操作?我是一名 c 程序员,我不喜欢使用这个 hack。因为这些脚本应该可以相互调用,所以建议使用stdin/put方式。更好的想法?谢谢 =) 【参考方案1】:

您的问题是缓冲输出、未关闭的文件描述符以及使用 EOF 表示传输的部分结束。前两个问题可以解决,但最后一个需要不同的方法。稍后会详细介绍。

一步一步:

Python 正在使用缓冲 I/O,因此您可能希望通过在第一个 print 语句后添加一行 sys.stdout.flush() 来强制 Python 刷新输出。现在"Hello!\n" 被逐字符读取。

但是接下来的read 会阻塞,直到新字符到达或管道关闭。 Python 脚本的 STDOUT 仍处于打开状态,C++ 程序正在等待某些东西到达,但 Python 脚本本身也在等待一些输入。经典的死锁。

您可能会丢弃 python 脚本中的最后一个打印并尝试关闭其 STDOUT。由于read 阻塞,直到所有引用管道写入端的文件描述符都关闭,您必须在flush 之后添加os.close(sys.stdout.fileno())os.close(sys.stdout.fileno())

但仍有有效的文件描述符引用该管道的写入部分。还记得 C++ 源代码中的 dup2 吗?在这三个dup2 行之后,仍然有parentToChild[READ_FD]childToParent[WRITE_FD] 引用脚本STDIN 和STDOUT。所以我们必须关闭它们。在dup2s 之后添加close(parentToChild[READ_FD]);close(childToParent[WRITE_FD]);。现在 read 在 Python 脚本关闭 STDOUT 和 STDERR 时返回 0

接下来,父母发送"\"AElement\":\"Something\"\0" 并到达waitpid,当孩子退出时返回。但孩子仍在阅读 STDIN。所以你必须在waitpid之前添加close(parentToChild[WRITE_FD]);


现在是概念部分:你不能read(),直到它返回一个0(管道关闭),然后继续从那个关闭的管道读取。您的选择:

读取一次,直到管道关闭。不可能有第二条消息。 知道要读多少。提前或通过解释接收到的字节。 监控两个管道,例如使用poll(2) 并动态决定您是要读取还是写入。

顺便说一句:execlp 的参数是const char *file, const char *arg, ...,其中arg, ... 是通常的char *args[],以arg[0](!) 开头并以空指针结尾。请将该行更改为int retexe = execlp(target, target, (char*) NULL);


#!/usr/bin/python2.7

import os
import sys

print "Hello!"
sys.stdout.flush()
os.close(sys.stdout.fileno())
os.close(sys.stderr.fileno())

data = sys.stdin.read()
with open("data_received_by_child.txt", "w") as fp:
    print >>fp, data

#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <iostream>

#include <sys/wait.h>
#include <unistd.h>

using namespace std;

int main()

    const char *target = "./hello.py";

    enum PIPE_FILE_DESCRIPTERS 
        READ_FD = 0, WRITE_FD = 1
    ;

    /* Make pipes */
    int parentToChild[2]; /* Parent to child pipe */
    if (pipe(parentToChild) < 0)
    
        perror("Can't make pipe");
        exit(1);
    
    int childToParent[2]; /* Child to parent pipe */
    if (pipe(childToParent) < 0)
    
        perror("Can't make pipe");
        exit(1);
    

    /* Create a child to run command. */
    pid_t pid = fork();
    switch (pid)
    
        case -1:
            perror("Can't fork");
            exit(1);

        case 0: /* Child */
            close(parentToChild[WRITE_FD]);
            close(childToParent[READ_FD]);
            dup2(parentToChild[READ_FD], STDIN_FILENO);
            dup2(childToParent[WRITE_FD], STDOUT_FILENO);
            close(parentToChild[READ_FD]);
            close(childToParent[WRITE_FD]);
            execlp(target, target, (char *) NULL);
            perror("Can't execute target");
            exit(1);

        default: /* Parent */
            close(parentToChild[READ_FD]);
            close(childToParent[WRITE_FD]);
            cout << "Child " << pid << " process running..." << endl;
    

    /* Read data from child */
    string dataReadFromChild;
    char ch;
    int rc;
    while ((rc = read(childToParent[READ_FD], &ch, 1)) != 0)
    
        if (rc == -1) 
            if ((errno == EINTR) || (errno == EAGAIN)) 
                continue;
            
            perror("read() failed");
            exit(-1);
        
        dataReadFromChild += ch;
    
    close(childToParent[READ_FD]);
    cout << "End of file reached..." << endl;
    cout << "Data received was (" << dataReadFromChild.size() << "):" << endl;
    cout << dataReadFromChild << endl;

    /* Write data to child */
    cout << "starting writing" << endl;
    const char bufferW[] = "\"AElement\":\"Something\"\0";
    while (true) 
        int rc = write(parentToChild[WRITE_FD], bufferW, sizeof(bufferW));
        if (rc == -1) 
            if ((errno == EINTR) || (errno == EAGAIN)) 
                continue;
            
            perror("write() failed");
            exit(-1);
        
        break;
    
    close(parentToChild[WRITE_FD]);

    /* Wait for child to exit */
    int status;
    int retWait = waitpid(pid, &status, 0);
    cout << endl << "Child exit status is:  " << WEXITSTATUS(status) << endl << endl;

【讨论】:

感谢您提供大量信息并指出我所做/复制的每一个错误。几天后我会在这些地方工作。获取有关此主题的更多信息。程序通信的东西,管道,启动外部程序和僵尸。你能给我一本好书的名字,或者一些关键词,从哪里开始我的搜索? 我遇到的关于 POSIX 级别和 Linux 编程的最佳书籍是 Michael Kerrisk 的“The Linux Programming Interface”。我还喜欢 Donald Lewine 的“POSIX Programmer's Guide”(1991 年出版,现在免费提供)。但我最重要的参考资料是手册页。 谢谢! 对于第一次迭代这个主题,我喜欢有书。您可以通过手册页了解更多问题。 “Linux 编程接口”很棒。详细的章节。一切从头开始解释。再次感谢。

以上是关于C++ 启动脚本(bash、python ...)并使用 stdin/stdout 进行数据传输 [linux]的主要内容,如果未能解决你的问题,请参考以下文章

通过bash启动python脚本时权限被拒绝

Python测试进阶——动手编写Bash脚本启动Python监控程序并传递PID

如果进程死了,如何编写 bash 脚本来重新启动进程?

用于检查连接的串行端口的Bash脚本

Bash 脚本和 C++ 之间的持久 IPC

带有命令行参数的 C++ 的 Makefile 和 Bash 脚本