首页 文章

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

提问于
浏览
15

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

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

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

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

p.stdin.write("1\n5\n")
errors = getOutput(errQueue)
output = getOutput(outQueue)

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

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

Second try: 我试过这个解决方案,它对我有用:

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()

3 回答

  • 12

    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()
    

    其次,正如J.F.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
    
  • 3

    目前的答案都不适合我 . 最后,我有这个工作:

    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)
    

    使用此 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])
    

    需要注意的是:输入/输出始终使用换行符,每次写入后刷新子级的stdin,并使用子级stdout中的 readline() (函数中管理的所有内容) .

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

  • 0

    这是一个交互式shell . 你必须在一个单独的线程上运行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()
    

相关问题