11

As far as I understand if i type the following...

 python -i

... the python-interpreter will now read from stdin, behaving (obviously) like this:

 >>> print "Hello"
 Hello

I would expect it to do the same thing if I do this:

 echo 'print "Hello"' > /proc/$(pidof python)/fd/0

But this is the output ( beeing an actual empty line):

 >>> print "Hello"
 <empyline>

This to me looks like, it just took the print "Hello"\n and wrote it to stdout, yet did not interpret it. Why is that not working and what would I have to do to make it working?

9

Sending input to shells/interpreters in this way is very problem-prone and very difficult to get working in any reliable way.

The proper way is to use sockets, this is why they were invented, you can do this in command line using ncat nc or socat to bind a python process to a simple socket. Or write a simple python application that binds to port and listens for commands to interpret on a socket.

sockets can be local and not exposed to any web interface.


The problem is that if you start python from the command line, it is typically attached to your shell which is attached to a terminal, in fact we can see

$ ls -al /proc/PID/fd
lrwxrwxrwx 1 USER GROUP 0 Aug 1 00:00 0 -> /dev/pty1

so when you write to stdin of python, you are actually writing to the pty psuedo-terminal, which is a kernel device, not a simple file. It uses ioctl not read and write, so you will see output on your screen, but it will not be sent to the spawned process (python)

One way to replicate what you are trying is with a fifo or named pipe.

# make pipe
$ mkfifo python_i.pipe
# start python interactive with pipe input
# Will print to pty output unless redirected
$ python -i < python_i.pipe &
# keep pipe open 
$ sleep infinity > python_i.pipe &
# interact with the interpreter
$ echo "print \"hello\"" >> python_i.pipe

You can also use screen for input only

# start screen 
$ screen -dmS python python
# send command to input
$ screen -S python -X 'print \"hello\"'
# view output
$ screen -S python -x
| improve this answer | |
  • If you hold the pipe open (eg sleep 300 > python_i.pipe &) the other side won't close and python will continue to accept commands down the pipe. There is no EOF as such sent by echo. – roaima Aug 13 '17 at 5:54
  • @roaima you are right, I was mistaken in my understanding that echo sends the EOF when it closes the stream. This is not avoidable with | pipes, however , correct? – crasic Aug 13 '17 at 5:56
  • I was already down the fifo road, but echo something > fifo would cause it to get an EOF which would stop many applications. The sleep infinity > fifo workaround didn't cross my mid though, thank you! – Sheppy Aug 13 '17 at 6:00
  • 1
    actually continuing your idea, you can also do python -i <> fifo which will also prevent the EOF – Sheppy Aug 13 '17 at 6:45
11

Accessing /proc/PID/fd/0 doesn't access file descriptor 0 of process PID, it accesses the file which PID has open on file descriptor 0. This is a subtle distinction, but it matters. A file descriptor is a connection that a process has to a file. Writing to a file descriptor writes to the file regardless of how the file has been opened.

If /proc/PID/fd/0 is a regular file, writing to it modifies the file. The data isn't necessarily what the process will read next: it depends on the position attached to the file descriptor that the process is using to read the file. When a process opens /proc/PID/fd/0, it gets the same file as the other process, but the file positions are independent.

If /proc/PID/fd/0 is a pipe, then writing to it appends the data to the pipe's buffer. In that case, the process that's reading from the pipe will read the data.

If /proc/PID/fd/0 is a terminal, then writing to it outputs the data on a terminal. A terminal file is bidirectional: writing to it outputs the data, i.e. the terminal displays the text; reading from a terminal inputs the data, i.e. the terminal transmits user input.

Python is both reading and writing to the terminal. When you run echo 'print "Hello"' > /proc/$(pidof python)/fd/0, you're writing print "Hello" to the terminal. The terminal displays print "Hello" as instructed. The python process doesn't see anything, it's still waiting for input.

If you want to feed input to the Python process, you have to get the terminal to do it. See crasic's answer for ways to do that.

| improve this answer | |
2

Building off of what Gilles said, if we want to write to a process's stdin that is attached to a terminal, we actually need to send the information to the terminal. However, because a terminal serves as a form of input as well as output, when writing to it the terminal has no way of knowing that you want to write to a process running within it rather than the "screen".

Linux however has a non-posix way of simulating user input through an ioctl request called TIOCSTI (Terminal I/O Control - Simulate Terminal Input) which allows us to send characters to a terminal as if they were typed by a user.

I'm only superficially aware of how this works, but based on this answer, it should be possible to do this with something along the lines of

import fcntl, sys, termios

tty_path = sys.argv[1]

with open(tty_path, 'wb') as tty_fd:
    for line in sys.stdin.buffer:
        for byte in line:
            fcntl.ioctl(tty_fd, termios.TIOCSTI, bytes([byte]))

Some external resources:

http://man7.org/linux/man-pages/man2/ioctl.2.html

http://man7.org/linux/man-pages/man2/ioctl_tty.2.html

| improve this answer | |
  • Observe that the question is not specific to any particular operating system, and that TIOCSTI did not originate with Linux. Almost two years before this answer was written, people started to drop TIOCSTI for security reasons. unix.stackexchange.com/q/406690/5132 – JdeBP Feb 24 at 9:24
  • @JdeBP Hence my specifying "Linux" (although I'm not sure where it originated). And by "people", it seems you mean some BSDs? From what I read back when I wrote this, it seems that there was, in a much older implementation, a security risk that has since been patched, but that BSD still figured it "safer" to drop the ioctl altogether. I'm however very much unfamiliar with any of this, so I figured it better not to say that something was not possible on certain systems when I have no experience with that system. – Christian Reall-Fluharty Feb 24 at 20:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.