1

What I'd like to do is to, in Python, programmatically send a few initial commands via stdin to a process, and then pass input to the user to let them control the program afterward. The Python program should simply wait until the subprocess exits due to user input. In essence, what I want to do is something along the lines of:

import subprocess

p = subprocess.Popen(['cat'], stdin=subprocess.PIPE)

# Send initial commands.
p.stdin.write(b"three\ninitial\ncommands\n")
p.stdin.flush()

# Give over control to the user.
# …Although stdin can't simply be reassigned
# in post like this, it seems.
p.stdin = sys.stdin

# Wait for the subprocess to finish.
p.wait()

How can I pass stdin back to the user (not using raw_input, since I need the user's input to come into effect every keypress and not just after pressing enter)?

obskyr
  • 1,380
  • 1
  • 9
  • 25

2 Answers2

3

Unfortunately, there is no standard way to splice your own stdin to some other process's stdin for the duration of that process, other than to read from your own stdin and write to that process, once you have chosen to write to that process in the first place.

That is, you can do this:

proc = subprocess.Popen(...)  # no stdin=

and the process will inherit your stdin; or you can do this:

proc = subprocess.Popen(..., stdin=subprocess.PIPE, ...)

and then you supply the stdin to that process. But once you have chosen to supply any of its stdin, you supply all of its stdin, even if that means you have to read your own stdin.

Linux offers a splice system call (documentation at man7.org, documentation at linux.die.net, Wikipedia, linux pipe data from file descriptor into a fifo) but your best bet is probably a background thread to copy the data.

torek
  • 448,244
  • 59
  • 642
  • 775
  • So Python doesn't have any built-in way to forward input from stdin to a subprocess pipe? If not, do you happen to know of a way in Python to get input from the user that's low-level enough to send on every keypress and not just every newline? – obskyr Nov 14 '18 at 02:44
  • "keypress" is a difficult notion in general: you might be run from something that's not connected to a keyboard at all. If you *do* have a keyboard, there are various ways to read from it, depending on OS and other items. How portable do you wish to be? – torek Nov 14 '18 at 02:48
  • Not very portable at all – this particular thing's just going to run on a Raspberry Pi. There's a program (omxplayer) that does keyboard shortcuts via stdin (e.g. space for play/pause), and I'd like to run a few keyboard commands on startup and then let the user control the video until it ends. Could I perhaps just use `sys.stdin.read` over and over and send that into the pipe…? – obskyr Nov 14 '18 at 02:52
  • (As a side note, omxplayer supports DBUS commands too, which I might look into – but I'd rather not if I don't have to.) – obskyr Nov 14 '18 at 02:53
  • In that case (the thing runs Linux) you might want to borrow the input reading routines from the `curses` package: this will put the tty into character-at-a-time mode for you. But it might be much easier if whatever program you're running can do its own keyboard-mode-thing and take command-line setup, so that you don't have to relay characters like that. – torek Nov 14 '18 at 02:58
  • Well, yeah, I'll do as much as I can with command line options. I guess the answer on how to switch input to the user is "capture and forward the user's input" after all – and I suppose how to capture input in a satisfactory and timing-sensitive way is left as an exercise to the reader. From what I can tell, [this other question](https://stackoverflow.com/questions/510357/python-read-a-single-character-from-the-user) is a good resource for how to do that, and it's not entirely trivial. Thanks for the insight! – obskyr Nov 14 '18 at 03:06
0

So searching for this same thing, at least in my case, the pexpect library takes care of this:

https://pexpect.readthedocs.io/en/stable/

p = pexpect.spawn("ssh myhost")
p.sendline("some_line")
p.interact()

As by its name you can automate a lot of interaction before handing it over to the user.

Note, in your case you may want an output filter: Using expect() and interact() simultaneously in pexpect

Kevin Joyce
  • 91
  • 1
  • 4