Baxorr
Baxorr

Reputation: 318

How do I properly loop through subprocess.stdout

I'm creating a program where I need to use a powershell session and I found out how I could have a persistent session using the below code. However I want to loop through the new lines of the output of powershell when a command has been run. The for loop below is the only way i've found to do so but it expects an EOF and doesn't get it so it just lingers and the program never exits. How can I get the amount of new lines in stdout so I can properly loop through them?

from subprocess import Popen, PIPE

process = Popen(["powershell"], stdin=PIPE, stdout=PIPE)

def ps(command):
    command = bytes("{}\n".format(command), encoding='utf-8')
    process.stdin.write(command)
    process.stdin.flush()

    process.stdout.readline()
    return process.stdout.readline().decode("utf-8")

ps("echo hello world")

for line in process.stdout:
    print(line.strip().decode("utf-8"))

process.stdin.close()
process.wait()

Upvotes: 0

Views: 990

Answers (1)

ShadowRanger
ShadowRanger

Reputation: 155363

You need the Powershell command to know when to exit. Typically, the solution is to not just flush, but close the stdin for the child process; when it's done with its work and finds EOF on its input, it should exit on its own. Just change:

process.stdin.flush()

to:

process.stdin.close()

which implies a flush and also ensures the child process knows input is done. If that doesn't work on its own, you might explicitly add a quit or exit (whatever Powershell uses to terminate the session manually) command after the command you're actually running.

If you must run multiple commands in the single subprocess, and each command must be fully consumed before the next one is sent, there are terrible heuristic solutions available, e.g. sending three commands at once, where the second simply echoes a sentinel string and the third explicitly flushes stdout (to ensure block buffering doesn't mean you deadlock waiting for the sentinel when its stuck in subprocess's internal buffers), and your loop can terminate once it sees the sentinel. Without a sentinel, it's worse, because you basically can't tell when the command is done, and just have to use the select/selectors module to poll the process's stdout with a timeout, reading lines whenever there is available data, and assuming the process is done if no new input is available without the expected timeout window.

Upvotes: 1

Related Questions