Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Interactive input/output using 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.

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\n") p.stdin.flush() errors = getOutput(errQueue) output = getOutput(outQueue)  p.stdin.write("5\n") 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\n5\n") errors = getOutput(errQueue) output = getOutput(outQueue) 

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


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.

Second try:

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\n") out = fr.read() p.stdin.write("5\n") out = fr.read() fw.close() fr.close() 
like image 521
Talor Abramovich Avatar asked Nov 09 '13 17:11

Talor Abramovich


People also ask

How do you make interactive inputs in Python?

Getting Interactive Python Input in the ShellThe input() command allows you to require a user to enter a string or number while a program is running. The input() method replaced the old raw_input() method that existed in Python v2. Open a terminal and run the python command to access Python.

What is interactive shell in Python?

Python interactive shell is also known as Integrated Development Environment (IDLE). With the Python installer, two interactive shells are provided: one is IDLE (Python GUI) and the other is Python (command line). Both can be used for running simple programs.

How do you print and input in Python?

In Python, we use the print() function to output data to the screen. Sometimes we might want to take the input from the user. We can do so by using the input() function. Python takes all the input as a string input by default.


2 Answers

None of the current answers worked for me. At the end, I've got this working:

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) 

Tested with this dummy.py script:

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

The caveats are (all managed in the functions):

  • Input/output always lines with newline.
  • Flush child's stdin after every write.
  • Use readline() from child's stdout.

It's a pretty simple solution in my opinion (not mine, I found it here: https://eli.thegreenplace.net/2017/interacting-with-a-long-running-child-process-in-python/). I was using Python 3.6.

like image 154
gorcajo Avatar answered Sep 28 '22 02:09

gorcajo


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

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\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 
like image 26
Talor Abramovich Avatar answered Sep 28 '22 03:09

Talor Abramovich