Reactionic
Reactionic

Reputation: 304

How to write subprocess' stdin continuously?

I have a script behaves like subscript.py below and I want to run it from another one because it was written with Python 2.7. I tried some of subprocess functions but none of them provides the same functionality like subscript runs in terminal natively.

subscript.py

import threading
from queue import Queue
import argparse


class FooBar():
    def __init__(self) -> None:
        self.q = Queue()
        foo = threading.Thread(target=self.foo)
        bar = threading.Thread(target=self.bar)
        foo.start()
        bar.start()
    

def foo(self):
    while True:
        text = input()
        print(f"{text} is sent by foo()")
        self.q.put(text)


def bar(self):
    while True:
        if not self.q.empty():
            text = self.q.get_nowait()
            print(f"{text} is sent by bar()")

if __name__ == "__main__":
    c = FooBar()

main1.py This one kills process right after something printed by subscript.

import subprocess
subprocess.run("py subscript.py", text=True, input="foo\n")

main2.py This one is a blocking type. So it is not possible to feed subscript with stdin.

import subprocess
output = subprocess.call("py subscript.py")

Upvotes: 1

Views: 1924

Answers (1)

tdelaney
tdelaney

Reputation: 77407

Your first option closed stdin as soon as the string was written and the second doesn't give you any opportunity to write to stdin so it hangs. You can use the lower-level Popen call to get a pipe to stdin and write it yourself.

>>> import sys
>>> import subprocess
>>> proc = subprocess.Popen([sys.executable, "subscript.py"], stdin=subprocess.PIPE)
>>> proc.stdin.write(b"hello\n")
6
>>> proc.stdin.flush()
>>> hello is sent by foo()
hello is sent by bar()

That pipe requires byte objeccts. But it can be put in a text wrapper that does the string to bytes encoding for you (and lets you use print if you want).

>>> import io
>>> txt_stdin = io.TextIOWrapper(proc.stdin)
>>> print("hello again", file=txt_stdin, flush=True)
>>> hello again is sent by foo()
hello again is sent by bar()

But there is still a lot missing. If the program writes things too, you may need to use pipes for stdout and stderr also. If it is expecting a terminal, you may need to use a pseudo-tty or pexpect if you are also on a unix like system.

Upvotes: 3

Related Questions