用pstream包装命令行程序

时间:2016-02-04 15:06:57

标签: c++ iostream popen

我希望能够从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

但是如果我把“询问输入”部分留下(即取消注释raw_input()),我得到的只是空白,甚至不是start,而是看起来像是等待输入的程序。 / p>

我的问题是,如何与这个pstream进行交互,如何建立一个小的读写 - 读 - 写会话?为什么该计划甚至没有显示start或第一个hello

修改
我似乎没有取得多大进展。我认为我真的不知道发生了什么。以下是一些评论的进一步尝试。

1)似乎我可以成功输入raw_input
我通过写信给孩子的stderr来证明这一点:

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管道仍处于打开状态时完成。

问题:是否有人可以提供有关如何编码此顺序通信的实际示例?

1 个答案:

答案 0 :(得分:2)

  

但如果我离开&#34;请求输入&#34;参与(即未注释的raw_input()),我得到的只是空白,甚至没有开始,而是等待输入的程序。

Python进程 等待来自其stdin的输入,该stdin连接到C ++程序中的管道。如果你没有写入pstream,那么Python进程永远不会收到任何东西。

你没有看到&#34;开始&#34;是Python认为它没有连接到终端,所以它不会打扰每次写入stdout。在Python程序中打印后尝试import sys然后sys.stdout.flush()。如果你需要它是交互式的,那么你需要定期刷新,或者将stdout设置为非缓冲模式(我不知道如何在Python中执行此操作)。

您还应该知道,在循环中仅使用getline将阻止等待更多输入,如果Python进程阻止等待输入,则会出现死锁。请参阅pstreams home page上的使用示例,其中显示了如何使用readsome()进行非阻塞读取。这将允许您尽可能多地读取,处理它,然后将响应发送回子进程,以便产生更多输出。

修改

  

我不认为我真正掌握了正在发生的事情。

你的问题不是pstreams或python的问题,你只是没有考虑两个通信进程之间的交互以及每个进程的等待。

获取笔和纸并绘制状态图或某种图表,以显示两个流程的位置以及它们正在等待的内容。

  

1)似乎我可以成功提供raw_input

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

这意味着python进程不断尝试从其stdin读取。父C ++进程写入三个字符然后退出,运行关闭管道的pstream析构函数。关闭管道导致Python进程因此得到EOF,所以它停止读取(仅获得三个字符而不是整行)。然后Python进程打印到stderr,它连接到你的终端,因为你没有告诉pstream将一个管道连接到孩子的stderr,所以你看到了那个输出。 / p>

  

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

因为现在父C ++进程没有退出,所以不关闭管道,因此子Python进程不会读取EOF并继续等待更多输入。父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()读取。您只需要在阅读两者 stdout和stderr时使用它,您可以使用proc.out()proc.err()来区分它们。

  

2)我无法获得完全可行的方法

同样,你有同样的问题,你只需要写三个字符,所以Python进程永远等待。 C ++进程也试图读取,所以它也会永远等待。死锁。

如果你通过发送一个换行符来修复它(如上所示)你还有另一个问题:C ++程序运行得如此之快,以至于它会进入while循环,在Python之前调用readsome过程甚至开始了。它将在管道中找不到任何内容,因此第一个readsome调用返回0并退出循环。然后C ++程序进入第二个while循环,子python进程仍然尚未开始打印任何内容,因此循环也不会读取任何内容并退出。然后整个C ++程序退出,最后Python子程序准备运行并尝试打印&#34; hello&#34;但到那时它的父母已经离开,它无法写入管道。

如果您没有时间阅读第一次时间,那么您需要readsome继续尝试,因此它等待的时间足以让第一个数据可读。< / p>

对于你的简单程序,你真的不需要readsome,因为Python进程一次只写一行,所以你可以用getline来读它。但是,如果它可能会写多行,那么您需要能够继续阅读,直到没有更多数据到来,readsome可以执行(只有在有可用的数据时才会读取)。但是你还需要一些方法来判断是否还有更多的数据(可能是孩子在发送更多数据之前忙于进行一些计算),或者它是否真的完成了。没有一般的方法可以知道,这取决于子进程正在做什么。也许您需要孩子发送一些哨兵值,例如"---END OF RESPONSE---",父母可以寻找知道何时停止尝试阅读更多内容。

出于简单示例的目的,我们假设如果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()还没有读取任何内容 ,那么你应该循环并重试。这使得C ++程序等待足够长的时间让Python程序打印出来。

您可能希望将while循环拆分为一个单独的函数,该函数将整个回复读入std::string并返回它,以便您可以每次重复使用该函数你想要阅读回复的时间。如果孩子发送了一些标记值,那么该函数很容易编写,因为它只会在每次收到标记字符串时停止。