使用上游包装命令行程序

Wrapping a commandline program with pstream

我希望能够从 C++ 读取和写入程序。 pstream好像可以做这个,但是我觉得文档很难理解,还没有找到例子。

我已经设置了以下最低工作示例。这将打开 python,进而 (1) 打印 hello (2) 询问输入,以及 (3) 打印 hello2:

#include <iostream>
#include <cstdio>
#include "pstream.h"

using namespace std;

int main(){
    std::cout << "start";
    redi::pstream proc(R"(python -c "if 1:
        print 'hello'
        raw_input()
        print 'hello2'
        ")");

    std::string line;
    //std::cout.flush();

    while (std::getline(proc.out(), line)){
        std::cout << " " << "stdout: " << line << '\n';
    }

    std::cout << "end";
    return 0;
}

如果我 运行 将 "ask input" 部分注释掉(即 #raw_input()),我将得到输出:

start stdout: hello
 stdout: hello2
end

但是如果我将 "ask input" 部分留在(即未注释的 raw_input())中,我得到的只是空白,甚至 start 都没有,而是看起来像是一个程序在等待输入。

我的问题是,如何与这个 pstream 交互,如何建立一个小 read-write-read-write 会话?为什么程序甚至不显示 start 或第一个 hello

编辑:
我似乎没有太大的进步。我不认为我真的了解发生了什么。下面是一些带有评论的进一步尝试。

1) 看来我可以成功喂 raw_input
我通过写信给 child 的标准错误来证明这一点:

int main(){    
    cout << "start" <<endl;
    redi::pstream proc(R"(python -c "if 1:
        import sys

        print 'hello'
        sys.stdout.flush()

        a = raw_input()
        sys.stdin.flush()

        sys.stderr.write('hello2 '+ a)
        sys.stderr.flush()

        ")");

    string line;
    getline(proc.out(), line);
    cout << line << endl;

    proc.write("foo",3).flush();

    cout << "end" << endl;
    return 0;
}

输出:

start
hello
end
hello2 foo

但是如果我再次尝试从 stdout 读取它会锁定

int main(){
        ...
        a = raw_input()
        sys.stdin.flush()

        print 'hello2', a
        sys.stdout.flush()
        ")");

    ...
    proc.write("foo",3).flush();

    std::getline(proc.out(), line);
    cout << line << endl;
    ...
}

产出

start
hello

2) 我根本无法使用可读的方法

int main(){
    cout << "start" <<endl;
    redi::pstream proc(R"(python -c "if 1:
        import sys

        print 'hello'
        sys.stdout.flush()

        a = raw_input()
        sys.stdin.flush()
        ")");

    std::streamsize n;
    char buf[1024];
    while ((n = proc.out().readsome(buf, sizeof(buf))) > 0)
                    std::cout.write(buf, n).flush();

    proc.write("foo",3).flush();

    cout << "end" << endl;
    return 0;
}

产出

start
end
Traceback (most recent call last):
  File "<string>", line 5, in <module>
IOError: [Errno 32] Broken pipe

输出包含 Python 错误,似乎 C++ 程序已完成,而 Python 管道仍处于打开状态。

问题:谁能提供一个工作示例来说明应该如何对这种顺序通信进行编码?

But if I leave the "ask input" part in (i.e. uncommented raw_input()), all I get is blank, not even start, but rather what seems like a program waiting for input.

Python进程正在等待来自其标准输入的输入,它连接到您的 C++ 程序中的管道。如果您不写入 pstream,那么 Python 进程将永远不会收到任何东西。

你看不到 "start" 的原因是 Python 认为它没有连接到终端,所以它不会在每次写入 stdout 时刷新。在 Python 程序中打印后尝试 import sys 然后 sys.stdout.flush()。如果你需要它是交互式的,那么你需要定期刷新,或者将 stdout 设置为 non-buffered 模式(我不知道如何在 Python 中做到这一点)。

你还应该知道,在循环中只使用 getline 会阻塞等待更多输入,如果 Python 进程 也会阻塞 等待输入你有一个僵局。请参阅 pstreams home page 上的用法示例,展示如何使用 readsome() 进行 non-blocking 读取。这将允许您尽可能多地阅读、处理它,然后将响应发送回 child 进程,以便它产生更多输出。

编辑:

I don't think I really grasp what is going on.

你的问题并不是 pstream 或 python 的真正问题,你只是没有考虑两个通信进程之间的交互以及每个进程在等待什么。

拿笔和纸画状态图或某种图表,显示两个进程到达的位置以及它们正在等待什么。

1) It seems like I can successfully feed raw_input

是的,但你做错了。 raw_input()读一行,你不是在写一行,你在写三个字符,"foo"。那不是一条线。

这意味着 python 进程不断尝试从其标准输入中读取。 parent C++ 进程写入三个字符然后退出,运行ning 关闭管道的 pstream 析构函数。关闭管道会导致 Python 进程得到 EOF,因此它停止读取(在只得到三个字符而不是整行之后)。 Python 进程然后打印到连接到您的终端的 stderr,因为您没有告诉 pstream 将管道附加到 child 的 stderr,所以您看到那个输出。

But it locks if I try to read from the stdout again

因为现在 parent C++ 进程没有退出,所以没有关闭管道,所以 child Python 进程没有读取 EOF 并一直等待更多的投入。 parent C++ 进程也在 等待输入,但永远不会到来。

如果你想发送一行供raw_input()读取,那么写一个换行符!

这很好用,因为它发送换行符,导致 Python 进程通过 raw_input() 行:

cout << "start" <<endl;
redi::pstream proc(R"(python -c "if 1:
    import sys

    print 'hello'
    sys.stdout.flush()

    a = raw_input()

    print 'hello2', a
    sys.stdout.flush()

    ")");

string line;
getline(proc, line);
cout << line << endl;

proc << "foo" << endl; // write to child FOLLOWED BY NEWLINE!

std::getline(proc, line); // read child's response
cout << line << endl;

cout << "end" << endl;

N.B。您不需要使用 proc.out() 因为您没有将管道附加到进程的 stderr,所以它总是从 proc.out() 读取。您只需要在从 both stdout 和 stderr 读取时使用它,您将在其中使用 proc.out()proc.err() 来区分它们。

2) I can't get the readsome approach to work at all

同样,您遇到了同样的问题,即您只写了三个字符,因此 Python 进程将永远等待。 C++ 进程也在尝试读取,因此它也会永远等待。死锁。

如果你通过发送一个换行符来解决这个问题(如上所示),你会遇到另一个问题:C++ 程序将 运行 如此之快以至于它会到达 while 循环调用 readsome 在 Python 进程甚至开始之前。它会在管道中找不到任何可读取的内容,因此第一个 readsome 调用 returns 0 并退出循环。然后 C++ 程序进入第二个 while 循环, child python 进程 still 还没有开始打印任何东西,所以循环也什么都不读并退出。然后整个 C++ 程序退出,最后 Python child 准备好 运行 并尝试打印 "hello" 但到那时它的 parent 已经消失了,它无法写入管道。

如果第一次调用它_时没有任何内容可读取,则需要 readsome 继续尝试_,因此它等待足够长的时间让第一个数据可读。

对于您的简单程序,您并不需要 readsome,因为 Python 进程一次只写入一行,因此您可以使用 getline 读取它。但是,如果它可能写入不止一行,您需要能够继续读取,直到没有更多数据到来,readsome 可以做到这一点(只有在有可用数据时才会读取)。但是您还需要一些方法来判断是否还会有更多数据(可能 child 在发送更多数据之前正忙于做一些计算)或者是否真的完成了。没有通用的方法可以知道,这取决于 child 进程在做什么。也许您需要 child 发送一些标记值,例如 "---END OF RESPONSE---",parent 可以查找该值以了解何时停止尝试阅读更多内容。

为了您的简单示例,我们假设如果 readsome 获得超过 4 个字节,它会收到整个响应:

cout << "start" <<endl;
redi::pstream proc(R"(python -c "if 1:
    import sys

    print 'hello'
    sys.stdout.flush()

    a = raw_input()
    sys.stdin.flush()

    print 'hello2', a
    sys.stdout.flush()
    ")");

string reply;
streamsize n;
char buf[1024];
while ((n = proc.readsome(buf, sizeof(buf))) != -1)
{
    if (n > 0)
        reply.append(buf, n);
    else
    {
        // Didn't read anything.  Is that a problem?
        // Need to try to process the content of 'reply' and see if
        // it's what we're expecting, or if it seems to be incomplete.
        //
        // Let's assume that if we've already read more than 4 characters
        // it's a complete response and there's no more to come:
        if (reply.length() > 3)
            break;
    }
}
cout << reply << std::flush;

proc << "foo" << std::endl;

while (getline(proc, reply))   // maybe use readsome again here
    cout << reply << std::endl;

cout << "end" << endl;

这会在 readsome() != -1 时循环,因此如果它没有读取任何内容,它会不断重试,只有在出现错误时才会停止循环。在循环体内它决定什么如果什么都没有读到。您需要在此处插入您自己的逻辑,这对您尝试做的任何事情都有意义,但基本上如果 readsome() 尚未阅读 anything,那么您应该循环并重试。这使得 C++ 程序等待足够长的时间让 Python 程序打印一些东西。

您可能希望将 while 循环拆分成一个单独的函数,将整个回复读入 std::string 和 returns,这样您就可以 re-use 每次你想阅读响应时的功能。如果 child 发送一些标记值,该函数将很容易编写,因为它会在每次收到标记字符串时停止。