使用上游包装命令行程序
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 发送一些标记值,该函数将很容易编写,因为它会在每次收到标记字符串时停止。
我希望能够从 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 发送一些标记值,该函数将很容易编写,因为它会在每次收到标记字符串时停止。