There's a console program I want to run from a python script. Let me call it the child. Once in a while, to continue processing, the child expects to read 0 bytes of data from stdin. For simplicity, let's assume the child is the following python script:
child.py
import os
import sys
import time
stdin = sys.stdin.fileno()
def speak(message):
print(message, flush=True, end="")
while True:
speak("Please say nothing!")
data = os.read(stdin, 1024)
if data == b"":
speak("Thank you for nothing.")
time.sleep(5)
else:
speak("I won't continue unless you keep silent.")
To work successfully (i.e. seeing "Thank you for nothing." printed), you must typically hit Ctrl+D
when running it in a UNIX terminal, while in cmd or PowerShell under Windows hitting Ctrl+Z
followed by Enter
will do the trick.
Here's an attempt to run the child from inside a python script, which I shall call the parent:
parent.py
import os
import subprocess
def speak_to_child(child_stdin_r, child_stdin_w, message):
child = subprocess.Popen(
["python", "child.py"],
stdin=child_stdin_r,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
child_stdout_r = child.stdout.fileno()
while True:
data = os.read(child_stdout_r, 1024)
print(f"child said: {data}")
if data == b"Please say nothing!":
os.write(child_stdin_w, message)
child_stdin_r, child_stdin_w = os.pipe()
speak_to_child(child_stdin_r, child_stdin_w, b"Not sure how to say nothing.")
This is of course an unsuccessful attempt as the child will clearly answer with "I won't continue unless you keep silent." after reading "Not sure how to say nothing." from its stdin.
Naively changing the message b"Not sure how to say nothing."
in the parent to the empty message b""
doesn't get rid of the problem, since writing 0 bytes to a pipe won't cause a read of 0 bytes on the receiving end.
Now on UNIX we could easily solve the problem by replacing the pipe with a pseudoterminal and the empty message b""
with an EOT character b"\x04"
like so:
import pty
child_stdin_w, child_stdin_r = pty.openpty()
speak_to_child(child_stdin_r, child_stdin_w, b"\x04")
This works ... but evidently not on Windows. So now my questions:
- On UNIX, is using a pseudoterminal the best way to force the read of 0 bytes or is there a better way?
- On Windows, given that pseudoterminals aren't available, how can I solve the problem?
A platform agnostic solution would of course be ideal.