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

Tal*_*ich 29 python stdin subprocess interactive stdout

我有一个与用户交互的程序(就像一个shell),我想以交互方式使用python子进程模块运行它.这意味着,我希望有可能写入stdin并立即从stdout获取输出.我在这里尝试了许多解决方案,但它们似乎都不能满足我的需求.

我编写的代码基于在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\n")
p.stdin.flush()
errors = getOutput(errQueue)
output = getOutput(outQueue)

p.stdin.write("5\n")
p.stdin.flush()
erros = getOutput(errQueue)
output = getOutput(outQueue)
Run Code Online (Sandbox Code Playgroud)

问题是队列保持为空,就好像没有输出一样.只有当我向stdin写入程序需要执行和终止的所有输入时,我才得到输出(这不是我想要的).例如,如果我做了类似的事情:

p.stdin.write("1\n5\n")
errors = getOutput(errQueue)
output = getOutput(outQueue)
Run Code Online (Sandbox Code Playgroud)

有什么办法可以做我想做的事吗?

编辑: 该脚本将在Linux机器上运行.我更改了我的脚本并删除了universal_newlines = True +将bufsize设置为1并在wrtie之后立即刷新stdin.我仍然没有输出.

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

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\n")
out = fr.read()
p.stdin.write("5\n")
out = fr.read()
fw.close()
fr.close()
Run Code Online (Sandbox Code Playgroud)

Tal*_*ich 16

Linux上针对此问题的两种解决方案:

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

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\n")
out = fr.read()
p.stdin.write("5\n")
out = fr.read()
fw.close()
fr.close()
Run Code Online (Sandbox Code Playgroud)

其次,正如JF Sebastian所提出的那样,是使用fnctl模块使p.stdout和p.stder管道无阻塞:

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\n")
while True:
    try:
        out1 = p.stdout.read()
    except IOError:
        continue
    else:
        break
out1 = p.stdout.read()
p.stdin.write("5\n")
while True:
    try:
        out2 = p.stdout.read()
    except IOError:
        continue
    else:
        break
Run Code Online (Sandbox Code Playgroud)


gor*_*ajo 11

当前的答案都没有对我有用。最后,我已经完成了这个工作:

import subprocess


def start(executable_file):
    return subprocess.Popen(
        executable_file,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )


def read(process):
    return process.stdout.readline().decode("utf-8").strip()


def write(process, message):
    process.stdin.write(f"{message.strip()}\n".encode("utf-8"))
    process.stdin.flush()


def terminate(process):
    process.stdin.close()
    process.terminate()
    process.wait(timeout=0.2)


process = start("./dummy.py")
write(process, "hello dummy")
print(read(process))
terminate(process)
Run Code Online (Sandbox Code Playgroud)

使用此dummy.py脚本进行了测试:

#!/usr/bin/env python3.6

import random
import time

while True:
    message = input()
    time.sleep(random.uniform(0.1, 1.0)) # simulates process time
    print(message[::-1])
Run Code Online (Sandbox Code Playgroud)

需要注意的是:输入/输出始终以换行符开头,每次写入后都刷新子标准输入,并readline()从子标准输出中使用(所有在函数中管理的标准输出)。

我认为这是一个非常简单的解决方案(不是我的,我在这里找到它:https : //eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/)。我正在使用Python 3.6。

  • 只是想评论说我已经尝试完成这个看似非常简单的任务大约 45 分钟了,结果 stdin.flush 成为了所有问题的解决方案。我在任何地方读过的任何其他答案中都没有看到这一行。 (4认同)

Sha*_*awn 6

这是一个交互式外壳。您必须在单独的线程上运行read(),否则它将阻止write()

import sys
import os
import subprocess
from subprocess import Popen, PIPE
import threading


class LocalShell(object):
    def __init__(self):
        pass

    def run(self):
        env = os.environ.copy()
        p = Popen('/bin/bash', stdin=PIPE, stdout=PIPE, stderr=subprocess.STDOUT, shell=True, env=env)
        sys.stdout.write("Started Local Terminal...\r\n\r\n")

        def writeall(p):
            while True:
                # print("read data: ")
                data = p.stdout.read(1).decode("utf-8")
                if not data:
                    break
                sys.stdout.write(data)
                sys.stdout.flush()

        writer = threading.Thread(target=writeall, args=(p,))
        writer.start()

        try:
            while True:
                d = sys.stdin.read(1)
                if not d:
                    break
                self._write(p, d.encode())

        except EOFError:
            pass

    def _write(self, process, message):
        process.stdin.write(message)
        process.stdin.flush()


shell = LocalShell()
shell.run()
Run Code Online (Sandbox Code Playgroud)

  • 谢谢你!我通过将 2 行更改为“p = Popen('/bin/bash', stdin=PIPE...” 到 `p = Popen(['cmd.exe'], stdin=PIPE... ` 并将 `data = p.stdout.read(1).decode("utf-8")` 行改为 `data = p.stdout.read(1).decode("cp437")`。只要有人愿意请改为在 Windows 上运行它。 (3认同)