使用 Python 进行交互式输入/输出

2022-01-18 00:00:00 python subprocess stdout stdin interactive

问题描述

我有一个与用户交互的程序(就像一个 shell),我想使用 Python 子进程模块交互地运行它.这意味着,我希望能够写入标准输入并立即从标准输出中获取输出.我尝试了这里提供的许多解决方案,但似乎没有一个能满足我的需求.

I have a program that interacts with the user (acts like a shell), and I want to run it using the Python subprocess module interactively. That means, I want the possibility to write to standard input and immediately get the output from standard output. I tried many solutions offered here, but none of them seems to work for my needs.

我编写的代码基于 运行交互式Python 中的命令.

The code I've written is based on Running an interactive command from within Python.

import Queue
import threading
import subprocess
def enqueue_output(out, queue):
    for line in iter(out.readline, b''):
        queue.put(line)
    out.close()

def getOutput(outQueue):
    outStr = ''
    try:
        while True: # Adds output from the queue until it is empty
            outStr += outQueue.get_nowait()

    except Queue.Empty:
        return outStr

p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize = 1)
#p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True)

outQueue = Queue()
errQueue = Queue()

outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))
errThread = Thread(target=enqueue_output, args=(p.stderr, errQueue))

outThread.daemon = True
errThread.daemon = True

outThread.start()
errThread.start()

p.stdin.write("1
")
p.stdin.flush()
errors = getOutput(errQueue)
output = getOutput(outQueue)

p.stdin.write("5
")
p.stdin.flush()
erros = getOutput(errQueue)
output = getOutput(outQueue)

问题是队列仍然是空的,好像没有输出一样.只有当我将程序需要执行和终止的所有输入写入标准输入时,我才会得到输出(这不是我想要的).例如,如果我这样做:

The problem is that the queue remains empty, as if there is no output. Only if I write to standard input all the input that the program needs to execute and terminate, then I get the output (which is not what I want). For example, if I do something like:

p.stdin.write("1
5
")
errors = getOutput(errQueue)
output = getOutput(outQueue)

有没有办法做我想做的事?

Is there a way to do what I want to do?

脚本将在 Linux 机器上运行.我更改了脚本并删除了 universal_newlines=True + 将 bufsize 设置为 1 并在写入后立即刷新标准输入.我仍然没有得到任何输出.

The script will run on a Linux machine. I changed my script and deleted the universal_newlines=True + set the bufsize to 1 and flushed standard input immediately after write. Still I don't get any output.

第二次尝试:

我尝试了这个解决方案,它对我有用:

I tried this solution and it works for me:

from subprocess import Popen, PIPE

fw = open("tmpout", "wb")
fr = open("tmpout", "r")
p = Popen("./a.out", stdin = PIPE, stdout = fw, stderr = fw, bufsize = 1)
p.stdin.write("1
")
out = fr.read()
p.stdin.write("5
")
out = fr.read()
fw.close()
fr.close()


解决方案

Linux 上这个问题的两种解决方案:

Two solutions for this issue on Linux:

第一个是使用文件将输出写入并同时读取:

First one is to use a file to write the output to, and read from it simultaneously:

from subprocess import Popen, PIPE

fw = open("tmpout", "wb")
fr = open("tmpout", "r")
p = Popen("./a.out", stdin = PIPE, stdout = fw, stderr = fw, bufsize = 1)
p.stdin.write("1
")
out = fr.read()
p.stdin.write("5
")
out = fr.read()
fw.close()
fr.close()

第二,正如 J.F. Sebastian 提供的,是使用 fnctl 模块使 p.stdout 和 p.stderr 管道非阻塞:

Second, as J.F. Sebastian offered, is to make p.stdout and p.stderr pipes non-blocking using fnctl module:

import os
import fcntl
from subprocess import Popen, PIPE  
def setNonBlocking(fd):
    """
    Set the file description of the given file descriptor to non-blocking.
    """
    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    flags = flags | os.O_NONBLOCK
    fcntl.fcntl(fd, fcntl.F_SETFL, flags)

p = Popen("./a.out", stdin = PIPE, stdout = PIPE, stderr = PIPE, bufsize = 1)
setNonBlocking(p.stdout)
setNonBlocking(p.stderr)

p.stdin.write("1
")
while True:
    try:
        out1 = p.stdout.read()
    except IOError:
        continue
    else:
        break
out1 = p.stdout.read()
p.stdin.write("5
")
while True:
    try:
        out2 = p.stdout.read()
    except IOError:
        continue
    else:
        break

相关文章