0

Sorry, I am really not a python person, but the situation hit me, and this will be something very obvious for you python people.

I am trying to communicate interactively with a spawned console process (in our use case it is a console program that communicates with an older, but still nice HP instrument for measuring some physics variables in an experiment).
I have found some insightful example here:
Running interactive program from within python

But when trying to get inspired (I do not want that timer), I started to write this from scratch, testing with an ftp.exe program that is typically present on a windows box, with the following:

#!/usr/bin/python
import subprocess, sys
mystdout=sys.stdout
p = subprocess.Popen("ftp.exe", stdin=subprocess.PIPE, stdout=subprocess.PIPE) #, shell=True)
sys.stdout=p.stdin
print('bye\n'.encode())
sys.stdout=mystdout
print('done')

But the print('bye\n'.encode()) results in:

print('bye\n'.encode())
TypeError: a bytes-like object is required, not 'str'

I can't use the communicate() method of subprocess as it seems to be not very interactive (one-time only).

Would you, please, give me a hint where is my dumb? This machine runs Python 3.6.1 on Windows, but a friendlier Linux machine with 3.7.3 gives the same greeting.

1 Answer 1

0

Interesting, I just figured out that adding

universal_newlines=True

as the last argument of Popen() solves my problem. I guess the stream did not like the print() because it was open in binary mode. Adding this flag opens the stream in text mode. I will post a complete example including interactive communication when I have it.

Edit:

Oh, now I found some more resources that are relevant to this issue.

It seems it is easier on Linux as there is the fcntl package that allows setup the pipes with non-blocking read(). Unfortunately, it seems it is not available on Windows :( I was hoping to use the peek() call, but surprise, surprise, it is also blocking on pipes! :(

So we are stuck either with threads or perhaps with the modern hard-to-read async/await style.

Based on the inspiration linked above, I have reached the following simple working ftp example:

#!/usr/bin/python

import time, sys, subprocess, threading, queue

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

def getOutput(outQueue):
    outStr = ''
    try:
        while True: 
            outStr+=outQueue.get_nowait()

    except queue.Empty:
        return outStr           

p = subprocess.Popen("ftp.exe", stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) 

outQueue = queue.Queue()
errQueue = queue.Queue()

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

outThread.daemon = True
errThread.daemon = True

outThread.start()
errThread.start()

p.stdin.write('status\n')
p.stdin.flush()
time.sleep(0.2)

errors = getOutput(errQueue)
output = getOutput(outQueue)

print("err:" + errors)
print("out:" + output)

time.sleep(2)

p.stdin.write('help\n')
p.stdin.flush()
time.sleep(0.2)

errors = getOutput(errQueue)
output = getOutput(outQueue)

print("err:" + errors)
print("out:" + output)

time.sleep(2)

p.stdin.write('bye\n')
p.stdin.flush()
time.sleep(0.2)

errors = getOutput(errQueue)
output = getOutput(outQueue)

print("err:" + errors)
print("out:" + output)

time.sleep(2)

print('done')

One last note: when using this to communicate with my own c++ program, I had to issue fflush(stdout) after each output. Not even a newline did help the stuff to fall into the pipe without flushing.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.