【问题标题】:subprocess readline hangs waiting for EOF子进程 readline 挂起等待 EOF
【发布时间】:2011-12-15 09:08:58
【问题描述】:

我有一个简单的 c++ 程序,我试图通过 python 脚本来执行它。 (我对编写脚本非常陌生)并且我无法通过管道读取输出。从我所看到的情况来看,如果没有 EOF,readline() 似乎将无法工作,但我希望能够在程序中间读取并让脚本响应输出的内容。而不是读取输出,它只是挂起 python脚本:

#!/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";
}

最终目标是让脚本通过二进制搜索解决问题,但现在我只想能够读取一行而不是文件末尾

【问题讨论】:

    标签: python subprocess stdout pipe readline


    【解决方案1】:

    作为@Ron Reiter pointed out,您不能使用readline(),因为cout 不会隐式打印换行符——您需要std::endl"\n" 这里。

    对于交互式使用,当你无法更改子程序时,pexpect module 提供了几种方便的方法(一般为it solves for free: input/output directly from/to terminal (outside of stdin/stdout) and block-buffering issues):

    #!/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)
    

    它有效,但它是二进制搜索,因此(traditionally) there could be bugs

    这是一个类似的代码,使用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】:

      您可能必须显式关闭stdin,因此子进程将停止挂起,我认为这是您的代码正在发生的事情——这可以通过在终端上运行 top 并检查是否有 randomnumber 来验证s 状态保持睡眠,如果它在预期的执行时间之后使用 0% CPU。

      简而言之,如果您在rng=subprocess(...) 调用之后立即添加rng.stdin.close(),它可能会毫无问题地恢复。另一种选择是执行output=rng.communicate(stdin="%d\n" % i) 并查看output[0]output[1],他们分别是stdoutstderr。你可以在communicatehere找到信息。

      【讨论】:

      • 哇,我忘了这个问题存在。如果您关闭标准输入,那么您将无法再与进程通信,这就是在这种情况下所需要的,动态响应输出
      • 没错。我认为您也许应该在答案中添加更新,以便其他读者了解挂起的真正原因,这很可能是他们到达这里的原因。另外,我不介意你提高我的答案:)
      • @RyanHaining:您的 C++ 代码不会检查标准输入上的 EOF,因此在您的情况下过早调用 rng.stdin.close() 会导致无限循环。在这种情况下,我会使用 pexpect 或其 Windows 模拟。
      • @RyanHaining +1 引用pexpect,这对我来说是全新的,对其他读者来说可能也是如此。您介意详细说明它将如何帮助解决给定的问题吗?
      • @LordHenryWotton 我不认为你打算在这件事上给我加标签,但 JF Sebastian 已经发布了详细的完整答案
      【解决方案3】:

      我很确定在您的 C++ 程序中添加换行符会导致 readlines 返回。

      【讨论】:

      • 是的,但我希望能够为现有程序制作脚本,而不是修改程序以适应脚本
      • 没有通用的方法来“解决”EOF 问题。否则,你怎么知道这条线实际上已经结束了?如果您知道该程序,请在换行之前不要阅读,只需阅读您需要的确切字符数。否则,使其返回换行符。您的另一种选择是使用超时,这真的很糟糕。
      • 您无需阅读确切的金额。 It is enough if you are nearly in sync(OS 管道缓冲区提供容差)。
      猜你喜欢
      • 2016-09-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 1970-01-01
      • 2020-02-16
      • 1970-01-01
      相关资源
      最近更新 更多