2017年5月12日 星期五

[ Python 常見問題 ] Interactive input/output using python

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

The code I've written based on Running an interactive command from within python: 
  1. import Queue  
  2. import threading  
  3. import subprocess  
  4.   
  5. def enqueue_output(out, queue):  
  6.     for line in iter(out.readline, b''):  
  7.         queue.put(line)  
  8.     out.close()  
  9.   
  10. def getOutput(outQueue):  
  11.     outStr = ''  
  12.     try:  
  13.         while True: #Adds output from the Queue until it is empty  
  14.             outStr+=outQueue.get_nowait()  
  15.   
  16.     except Queue.Empty:  
  17.         return outStr  
  18.   
  19. p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize = 1)  
  20. #p = subprocess.Popen("./a.out", stdin=subprocess.PIPE, stout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False, universal_newlines=True)  
  21.   
  22. outQueue = Queue()  
  23. errQueue = Queue()  
  24.   
  25. outThread = Thread(target=enqueue_output, args=(p.stdout, outQueue))  
  26. errThread = Thread(target=enqueue_output, args=(p.stderr, errQueue))  
  27.   
  28. outThread.daemon = True  
  29. errThread.daemon = True  
  30.   
  31. outThread.start()  
  32. errThread.start()  
  33.   
  34. p.stdin.write("1\n")  
  35. p.stdin.flush()  
  36. errors = getOutput(errQueue)  
  37. output = getOutput(outQueue)  
  38.   
  39. p.stdin.write("5\n")  
  40. p.stdin.flush()  
  41. erros = getOutput(errQueue)  
  42. output = getOutput(outQueue)  
The problem is that the queue remains empty, as if there is no output. Only if I write to stdin all the input that the program needs to execute and terminate, then I get the output (which is not what I want ). Is there any way to do what I want to do? 

How-To 
Second try: I tried this solution and it works for me. Firstly, a testing shell to echo with input line: 
- test.sh 
  1. #!/bin/sh  
  2. while read LINE; do  
  3.     echo "OUT> $LINE"  
  4.     if [ "$LINE" == "bye" ]; then  
  5.         break  
  6.     fi  
  7. done  
Usage: 
# ./test.sh
test line1
OUT> test line1
test line2
OUT> test line2
bye // Exit the interactive shell
OUT> bye

Then we can use python code to run this interactive shell: 
- test.py 
  1. from subprocess import Popen, PIPE  
  2. import time  
  3.   
  4.   
  5. class CmdInteractor:  
  6.     def __init__(self, cmd, tmp_file = "tmpout"):  
  7.         self.cmd = cmd  
  8.         self.fw = open(tmp_file, "wb")  
  9.         self.fr = fr = open(tmp_file, "r")  
  10.         self.p = Popen(cmd, shell=True, stdin = PIPE, stdout = self.fw, stderr = self.fw)  
  11.   
  12.     def sendline(self, msg):  
  13.         self.p.stdin.write(("%s\n" % (msg)).encode()); self.p.stdin.flush()  
  14.   
  15.     def readline(self, timeout=10):  
  16.         r'''  
  17.         Read one line from stdout  
  18.   
  19.         @param timeouot:  
  20.             Timeout in second  
  21.         '''  
  22.         out = self.fr.readline()  
  23.         wi = 0  
  24.         while out is None or out == '':  
  25.             wi = wi + 1  
  26.             if wi > 10:  
  27.                 raise Exception('Timeout in reading line from output!')  
  28.             time.sleep(1)  
  29.             out = self.fr.readline()  
  30.         return out.rstrip()  
  31.   
  32.     def __del__(self):  
  33.         self.fw.close()  
  34.         self.fr.close()  
  35.         self.p.terminate()  
  36.   
  37. ci = CmdInteractor('./test.sh')  
  38. ci.sendline('Test')  
  39. print("Read: %s" % (ci.readline()))  
  40. ci.sendline('Hello')  
  41. print("Read: %s" % (ci.readline()))  
  42. print("\t[Info] Say bye!")  
  43. ci.sendline('bye')  
  44. print("Read: %s" % (ci.readline()))  
Execution out: 
# ./test.py
Read: OUT> Test
Read: OUT> Hello
[Info] Say bye!
Read: OUT> bye

Supplement 
* Running an interactive command from within python 
* Python std library - subprocess — Subprocess management 

沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...