Подпроцесс readline висит в ожидании EOF

У меня есть простая программа на С++, которую я пытаюсь выполнить через python script. (Я очень новичок в написании сценариев), и у меня проблемы с чтением выходных данных через трубу. Из того, что я видел, похоже, что readline() не будет работать без EOF, но я хочу иметь возможность читать в середине программы и отвечать script на то, что выводится. Вместо чтения вывода он просто зависает python script:

#!/usr/bin/env python
import subprocess
def callRandomNumber():
    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()
callRandomNumber()

и файл С++, который генерирует случайное число от одного до 100, затем проверяет догадывание пользователей, пока они не угадают правильно

#include<iostream>
#include<cstdlib>
using namespace std;

int main(){
  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"<<endl;
  srand(time(NULL));
  int num=rand()%100;
  int guessCount=0;
  int guess=-1;
  cout<<"Please enter a number:  ";
  cin>>guess;
  while(guess!=num){
    if(guess>num){cout<<"That guess is too high.  Please guess again:  ";}
    else{cout<<"That guess is too low.  Please guess again:  ";}
    cin>>guess;
    guessCount++;
  }
  cout<<"Congratulations!  You solved it in "<<guessCount<<" guesses!"<<endl;
  return 0;
}

конечная цель состоит в том, чтобы script решить проблему с двоичным поиском, но на данный момент я просто хочу иметь возможность читать строку, не будучи концом файла

Ответы

Ответ 1

Как @Ron Reiter указал, вы не можете использовать readline(), потому что cout не печатает новые строки неявно - вам либо нужно std::endl или "\n" здесь.

Для интерактивного использования, когда вы не можете изменить дочернюю программу, pexpect module предоставляет несколько удобных методов (и вообще он решает бесплатно: ввод/вывод непосредственно с/на терминал (вне 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)

Это работает, но это двоичный поиск, поэтому (традиционно) могут быть ошибки.

Здесь аналогичный код, который использует 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())

Ответ 2

Я уверен, что добавление новых строк в вашей программе на С++ приведет к возврату строк readlines.

Ответ 3

Возможно, вам придется явно закрыть stdin, поэтому дочерний процесс перестанет зависать, что, я думаю, является тем, что происходит с вашим кодом - это можно проверить, выполнив верхнюю часть терминала и проверив, соответствует ли статус randomnumber остается спать, и если он использует 0% процессор после ожидаемого времени, которое потребуется для его выполнения.

Короче говоря, если вы добавите rng.stdin.close() сразу после вызова rng=subprocess(...), он может возобновиться без проблем. Другой вариант - сделать output=rng.communicate(stdin="%d\n" % i) и посмотреть output[0] и output[1], которые являются stdout и stderr соответственно. Вы можете найти информацию о communicate здесь.