子进程 readline 挂起等待 EOF

6 投票
3 回答
9714 浏览
提问于 2025-04-17 05:00

我有一个简单的C++程序,想通过一个Python脚本来执行。(我刚开始写脚本)但是我在通过管道读取输出时遇到了麻烦。从我看到的情况来看,readline()好像在没有文件结束符(EOF)的情况下无法工作,但我希望能够在程序运行的过程中读取输出,并让脚本对输出做出反应。结果是,它只是在那儿卡住了。

#!/usr/bin/env python
import subprocess
def call_random_number():
    print "Running the random guesser"
    rng = subprocess.Popen("./randomNumber", stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell=True)
    i = 50
    rng.stdin.write("%d\n" % i)
    output = rng.stdout.readline()
    output = rng.stdout.readline()

call_random_number()

这个C++文件会生成一个1到100之间的随机数,然后检查用户的猜测,直到猜对为止。

#include<iostream>
#include<cstdlib>

int main(){
  std::cout<< "This program generates a random number from 1 to 100 and asks the user to enter guesses until they succuessfully guess the number.  It then tells the user how many guesses it took them\n";
  std::srand(std::time(NULL));
  int num = std::rand() % 100;
  int guessCount = 0;
  int guess = -1;
  std::cout << "Please enter a number:  ";
  std::cin >> guess;
  while(guess != num){
    if (guess > num){
        std::cout << "That guess is too high.  Please guess again:  ";
    } else {
        std::cout << "That guess is too low.  Please guess again:  ";
    }
    std::cin >> guess;
    guessCount++;
  }
  std::cout << "Congratulations!  You solved it in " << guessCount << " guesses!\n";
}

最终的目标是让脚本用二分查找的方法来解决这个问题,但现在我只想在不是文件结束的时候读取一行输出。

3 个回答

0

你可能需要手动关闭stdin,这样子子进程就不会一直卡在那里,我觉得你的代码就是这个情况。你可以通过在终端运行top命令来验证这一点,看看randomnumber的状态是否一直是“睡眠”,并且在预期的执行时间后CPU使用率是否为0%。

简单来说,如果你在rng=subprocess(...)调用后立即添加rng.stdin.close(),可能就能顺利继续执行了。另一种方法是使用output=rng.communicate(stdin="%d\n" % i),然后查看output[0]output[1],它们分别代表stdoutstderr。你可以在这里找到关于communicate的更多信息。

1

我很确定在你的C++程序中添加换行符会导致读取行数返回。

5

正如@Ron Reiter提到的,你不能使用readline(),因为cout不会自动换行——你需要在这里使用std::endl或者"\n"

对于交互式使用,当你不能修改子程序时,pexpect模块提供了几个方便的方法(而且一般来说它可以免费解决:直接从终端输入/输出(不通过stdin/stdout)和阻塞缓冲问题):

#!/usr/bin/env python
import sys

if sys.version_info[:1] < (3,):
    from pexpect import spawn, EOF # $ pip install pexpect
else:
    from pexpect import spawnu as spawn, EOF # Python 3

child = spawn("./randomNumber") # run command
child.delaybeforesend = 0 
child.logfile_read = sys.stdout # print child output to stdout for debugging
child.expect("enter a number: ") # read the first prompt
lo, hi = 0, 100
while lo <= hi:
    mid = (lo + hi) // 2
    child.sendline(str(mid)) # send number
    index = child.expect([": ", EOF]) # read prompt
    if index == 0: # got prompt
        prompt = child.before
        if "too high" in prompt:
            hi = mid - 1 # guess > num
        elif "too low" in prompt:
            lo = mid + 1 # guess < num
    elif index == 1: # EOF
        assert "Congratulations" in child.before
        child.close()
        break
else:
    print('not found')
    child.terminate()
sys.exit(-child.signalstatus if child.signalstatus else child.exitstatus)

这个方法是有效的,但它是二分查找,因此(传统上)可能会有一些bug

这里有一段类似的代码,使用subprocess模块进行比较:

#!/usr/bin/env python
from __future__ import print_function
import sys
from subprocess import Popen, PIPE

p = Popen("./randomNumber", stdin=PIPE, stdout=PIPE,
          bufsize=1, # line-buffering
          universal_newlines=True) # enable text mode
p.stdout.readline() # discard welcome message: "This program gener...

readchar = lambda: p.stdout.read(1)
def read_until(char):
    buf = []
    for c in iter(readchar, char):
        if not c: # EOF
            break
        buf.append(c)
    else: # no EOF
        buf.append(char)
    return ''.join(buf).strip()

prompt = read_until(':') # read 1st prompt
lo, hi = 0, 100
while lo <= hi:
    mid = (lo + hi) // 2
    print(prompt, mid)
    print(mid, file=p.stdin) # send number
    prompt = read_until(':') # read prompt
    if "Congratulations" in prompt:
        print(prompt)
        print(mid)
        break # found
    elif "too high" in prompt:
        hi = mid - 1 # guess > num
    elif "too low" in prompt:
        lo = mid + 1 # guess < num
else:
    print('not found')
    p.kill()
for pipe in [p.stdin, p.stdout]:
    try:
        pipe.close()
    except OSError:
        pass
sys.exit(p.wait())

撰写回答